diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..b342a0d7ac --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,23 @@ +PLEASE DON'T FORGET TO "STAR" THIS REPOSITORY :) + +When reporting a bug please include the following: + +### Version of Beast + +You can find the version number in +or using the command "git log -1". + +### Steps necessary to reproduce the problem + +A small compiling program is the best. If your code is +public, you can provide a link to the repository. + +### All relevant compiler information + +If you are unable to compile please include the type and +version of compiler you are using as well as all compiler +output including the error message, file, and line numbers +involved. + +The more information you provide the sooner your issue +can get resolved! diff --git a/.gitignore b/.gitignore index 99f984bdaa..1740b535d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,7 @@ bin/ bin64/ + +# Because of CMake and VS2017 +Win32/ +x64/ + diff --git a/.travis.yml b/.travis.yml index 236e8da718..6a6ab527f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,17 +11,15 @@ env: # to boost's .tar.gz. - LCOV_ROOT=$HOME/lcov - VALGRIND_ROOT=$HOME/valgrind-install - - BOOST_ROOT=$HOME/boost_1_61_0 - - BOOST_URL='http://sourceforge.net/projects/boost/files/boost/1.61.0/boost_1_61_0.tar.gz' + - BOOST_ROOT=$HOME/boost_1_58_0 + - BOOST_URL='http://sourceforge.net/projects/boost/files/boost/1.58.0/boost_1_58_0.tar.gz' addons: apt: - sources: ['ubuntu-toolchain-r-test'] - packages: - - gcc-5 - - g++-5 + sources: &base_sources + - ubuntu-toolchain-r-test + packages: &base_packages - python-software-properties - - libssl-dev - libffi-dev - libstdc++6 - binutils-gold @@ -35,35 +33,81 @@ addons: matrix: include: - # GCC/Coverage/Autobahn (if master or develop branch) + # gcc coverage + - compiler: gcc + env: + - GCC_VER=6 + - VARIANT=coverage + - ADDRESS_MODEL=64 + - DO_VALGRIND=false + - BUILD_SYSTEM=cmake + - PATH=$PWD/cmake/bin:$PATH + addons: + apt: + packages: + - gcc-6 + - g++-6 + - libssl-dev + - *base_packages + sources: + - *base_sources + + # older GCC, release + - compiler: gcc + env: + - GCC_VER=4.8 + - VARIANT=release + - DO_VALGRIND=false + - ADDRESS_MODEL=64 + addons: + apt: + packages: + - gcc-4.8 + - g++-4.8 + - *base_packages + sources: + - *base_sources + + # later GCC - compiler: gcc env: - GCC_VER=5 - - VARIANT=coverage + - VARIANT=release + - DO_VALGRIND=true - ADDRESS_MODEL=64 - BUILD_SYSTEM=cmake - PATH=$PWD/cmake/bin:$PATH + addons: + apt: + packages: + - gcc-5 + - g++-5 + - libssl-dev + - *base_packages + sources: + - *base_sources - # Clang/UndefinedBehaviourSanitizer + # clang ubsan+asan - compiler: clang env: - GCC_VER=5 - - VARIANT=usan + - VARIANT=ubasan - CLANG_VER=3.8 + - DO_VALGRIND=false - ADDRESS_MODEL=64 - UBSAN_OPTIONS='print_stacktrace=1' - BUILD_SYSTEM=cmake - PATH=$PWD/cmake/bin:$PATH - PATH=$PWD/llvm-$LLVM_VERSION/bin:$PATH - - # Clang/AddressSanitizer - - compiler: clang - env: - - GCC_VER=5 - - VARIANT=asan - - CLANG_VER=3.8 - - ADDRESS_MODEL=64 - - PATH=$PWD/llvm-$LLVM_VERSION/bin:$PATH + addons: + apt: + packages: + - gcc-5 + - g++-5 + - libssl-dev + - *base_packages + sources: + - *base_sources cache: directories: @@ -72,7 +116,7 @@ cache: - llvm-$LLVM_VERSION - cmake -before_install: +before_install: &base_before_install - scripts/install-dependencies.sh script: diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dcb1650c7..803c8014ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,829 @@ +Version 79: + +* Remove spurious fallthrough guidance + +-------------------------------------------------------------------------------- + +Version 78: + +* Add span +* Documentation work +* Use make_unique_noinit +* Fix warning in zlib +* Header file tidying +* Tidy up FieldsReader doc +* Add Boost.Locale utf8 benchmark comparison +* Tidy up dstream for existing Boost versions +* Tidy up file_posix unused variable +* Fix warning in root ca declaration + +HTTP: + +* Tidy up basic_string_body +* Add vector_body +* span, string, vector bodies are public +* Fix spurious uninitialized warning +* fields temp string uses allocator + +API Changes: + +* Add message::keep_alive() +* Add message::chunked() and message::content_length() +* Remove string_view_body + +Actions Required: + +* Change user defined implementations of Fields and + FieldsReader to meet the new requirements. + +* Use span_body instead of string_view_body + +-------------------------------------------------------------------------------- + +Version 77: + +* file_posix works without large file support + +-------------------------------------------------------------------------------- + +Version 76: + +* Always go through write_some +* Use Boost.Config +* BodyReader may construct from a non-const message +* Add serializer::get +* Add serializer::chunked +* Serializer members are not const +* serializing file_body is not const +* Add file_body_win32 +* Fix parse illegal characters in obs-fold +* Disable SSE4.2 optimizations + +API Changes: + +* Rename to serializer::keep_alive +* BodyReader, BodyWriter use two-phase init + +Actions Required: + +* Use serializer::keep_alive instead of serializer::close and + take the logical NOT of the return value. + +* Modify instances of user-defined BodyReader and BodyWriter + types to perfrom two-phase initialization, as per the + updated documented type requirements. + +-------------------------------------------------------------------------------- + +Version 75: + +* Use file_body for valid requests, string_body otherwise. +* Construct buffer_prefix_view in-place +* Shrink serializer buffers using buffers_ref +* Tidy up BEAST_NO_BIG_VARIANTS +* Shrink serializer buffers using buffers_ref +* Add serializer::limit +* file_body tests +* Using SSE4.2 intrinsics in basic_parser if available + +-------------------------------------------------------------------------------- + +Version 74: + +* Add file_stdio and File concept +* Add file_win32 +* Add file_body +* Remove common/file_body.hpp +* Add file_posix +* Fix Beast include directories for cmake targets +* remove redundant flush() from example + +-------------------------------------------------------------------------------- + +Version 73: + +* Jamroot tweak +* Verify certificates in SSL clients +* Adjust benchmarks +* Initialize local variable in basic_parser +* Fixes for gcc-4.8 + +HTTP: + +* basic_parser optimizations +* Add basic_parser tests + +API Changes: + +* Refactor header and message constructors +* serializer::next replaces serializer::get + +Actions Required: + +* Evaluate each message constructor call site and + adjust the constructor argument list as needed. + +* Use serializer::next instead of serializer::get at call sites + +-------------------------------------------------------------------------------- + +Version 72: + +HTTP: + +* Tidy up set payload in http-server-fast +* Refine Body::size specification +* Newly constructed responses have a 200 OK result +* Refactor file_body for best practices +* Add http-server-threaded example +* Documentation tidying +* Various improvements to http_server_fast.cpp + +WebSocket: + +* Add websocket-server-async example + + +-------------------------------------------------------------------------------- + +Version 71: + +* Fix extra ; warning +* Documentation revision +* Fix spurious on_chunk invocation +* Call prepare_payload in HTTP example +* Check trailers in test +* Fix buffer overflow handling for string_body and mutable_body +* Concept check in basic_dynamic_body +* Tidy up http_sync_port error check +* Tidy up Jamroot /permissive- + +WebSockets: + +* Fine tune websocket op asserts +* Refactor websocket composed ops +* Allow close, ping, and write to happen concurrently +* Fix race in websocket read op +* Fix websocket write op +* Add cmake options for examples and tests + +API Changes: + +* Return `std::size_t` from `Body::writer::put` + +Actions Required: + +* Return the number of bytes actually transferred from the + input buffers in user defined `Body::writer::put` functions. + +-------------------------------------------------------------------------------- + +Version 70: + +* Serialize in one step when possible +* Add basic_parser header and body limits +* Add parser::on_header to set a callback +* Fix BEAST_FALLTHROUGH +* Fix HEAD response in file_service + +API Changes: + +* Rename to message::base +* basic_parser default limits are now 1MB/8MB + +Actions Required: + +* Change calls to message::header_part() with message::base() + +* Call body_limit and/or header_limit as needed to adjust the + limits to suitable values if the defaults are insufficient. + +-------------------------------------------------------------------------------- + +Version 69: + +* basic_parser optimizations +* Use BEAST_FALLTHROUGH to silence warnings +* Add /permissive- to msvc toolchain + +-------------------------------------------------------------------------------- + +Version 68: + +* Split common tests to a new project +* Small speed up in fields comparisons +* Adjust buffer size in fast server +* Use string_ref in older Boost versions +* Optimize field lookups +* Add const_body, mutable_body to examples +* Link statically on cmake MSVC + +API Changes: + +* Change BodyReader, BodyWriter requirements +* Remove BodyReader::is_deferred +* http::error::bad_target replaces bad_path + +Actions Required: + +* Change user defined instances of BodyReader and BodyWriter + to meet the new requirements. + +* Replace references to http::error::bad_path with http::error::bad_target + +-------------------------------------------------------------------------------- + +Version 67: + +* Fix doc example link +* Add http-server-small example +* Merge stream_base to stream and tidy +* Use boost::string_view +* Rename to http-server-fast +* Appveyor use Boost 1.64.0 +* Group common example headers + +API Changes: + +* control_callback replaces ping_callback + +Actions Required: + +* Change calls to websocket::stream::ping_callback to use + websocket::stream::control_callback + +* Change user defined ping callbacks to have the new + signature and adjust the callback definition appropriately. + +-------------------------------------------------------------------------------- + +Version 66: + +* string_param optimizations +* Add serializer request/response aliases +* Make consuming_buffers smaller +* Fix costly potential value-init in parser +* Fix unused parameter warning +* Handle bad_alloc in parser +* Tidy up message piecewise ctors +* Add header aliases +* basic_fields optimizations +* Add http-server example +* Squelch spurious warning on gcc + +-------------------------------------------------------------------------------- + +Version 65: + +* Enable narrowing warning on msvc cmake +* Fix integer types in deflate_stream::bi_reverse +* Fix narrowing in static_ostream +* Fix narrowing in ostream +* Fix narrowing in inflate_stream +* Fix narrowing in deflate_stream +* Fix integer warnings +* Enable unused variable warning on msvc cmake + +-------------------------------------------------------------------------------- + +Version 64: + +* Simplify buffered_read_stream composed op +* Simplify ssl teardown composed op +* Simplify websocket write_op +* Exemplars are compiled code +* Better User-Agent in examples +* async_write requires a non-const message +* Doc tidying +* Add link_directories to cmake + +API Changes: + +* Remove make_serializer + +Actions Required: + +* Replace calls to make_serializer with variable declarations + +-------------------------------------------------------------------------------- + +Version 63: + +* Use std::to_string instead of lexical_cast +* Don't use cached Boost +* Put num_jobs back up on Travis +* Only build and run tests in variant=coverage +* Move benchmarks to a separate project +* Only run the tests under ubasan +* Tidy up CMakeLists.txt +* Tidy up Jamfiles +* Control running with valgrind explicitly + +-------------------------------------------------------------------------------- + +Version 62: + +* Remove libssl-dev from a Travis matrix item +* Increase detail::static_ostream coverage +* Add server-framework tests +* Doc fixes and tidy +* Tidy up namespaces in examples +* Clear the error faster +* Avoid explicit operator bool for error +* Add http::is_fields trait +* Squelch harmless not_connected errors +* Put slow tests back for coverage builds + +API Changes: + +* parser requires basic_fields +* Refine FieldsReader concept +* message::prepare_payload replaces message::prepare + +Actions Required: + +* Callers using `parser` with Fields types other than basic_fields + will need to create their own subclass of basic_parser to work + with their custom fields type. + +* Implement chunked() and keep_alive() for user defined FieldsReader types. + +* Change calls to msg.prepare to msg.prepare_payload. For messages + with a user-defined Fields, provide the function prepare_payload_impl + in the fields type according to the Fields requirements. + +-------------------------------------------------------------------------------- + +Version 61: + +* Remove Spirit dependency +* Use generic_cateogry for errno +* Reorganize SSL examples +* Tidy up some integer conversion warnings +* Add message::header_part() +* Tidy up names in error categories +* Flush the output stream in the example +* Clean close in Secure WebSocket client +* Add server-framework SSL HTTP and WebSocket ports +* Fix shadowing warnings +* Tidy up http-crawl example +* Add multi_port to server-framework +* Tidy up resolver calls +* Use one job on CI +* Don't run slow tests on certain targets + +API Changes: + +* header::version is unsigned +* status-codes is unsigned + +-------------------------------------------------------------------------------- + +Version 60: + +* String comparisons are public interfaces +* Fix response message type in async websocket accept +* New server-framework, full featured server example + +-------------------------------------------------------------------------------- + +Version 59: + +* Integrated Beast INTERFACE (cmake) +* Fix base64 alphabet +* Remove obsolete doc/README.md + +API Changes: + +* Change Body::size signature (API Change): + +Actions Required: + +* For any user-defined models of Body, change the function signature + to accept `value_type const&` and modify the function definition + accordingly. + +-------------------------------------------------------------------------------- + +Version 58: + +* Fix unaligned reads in utf8-checker +* Qualify size_t in message template +* Reorganize examples +* Specification for http read +* Avoid `std::string` in websocket +* Fix basic_fields insert ordering +* basic_fields::set optimization +* basic_parser::put doc +* Use static string in basic_fields::reader +* Remove redundant code +* Fix parsing chunk size with leading zeroes +* Better message formal parameter names + +API Changes: + +* `basic_fields::set` renamed from `basic_fields::replace` + +Actions Required: + +* Rename calls to `basic_fields::replace` to `basic_fields::set` + +-------------------------------------------------------------------------------- + +Version 57: + +* Fix message.hpp javadocs +* Fix warning in basic_parser.cpp +* Integrate docca for documentation and tidy + +-------------------------------------------------------------------------------- + +Version 56: + +* Add provisional IANA header field names +* Add string_view_body +* Call on_chunk when the extension is empty +* HTTP/1.1 is the default version +* Try harder to find Boost (cmake) +* Reset error codes +* More basic_parser tests +* Add an INTERFACE cmake target +* Convert buffer in range loops + +-------------------------------------------------------------------------------- + +Version 55: + +* Don't allocate memory to handle obs-fold +* Avoid a parser allocation using non-flat buffer +* read_size replaces read_size_helper + +-------------------------------------------------------------------------------- + +Version 54: + +* static_buffer coverage +* flat_buffer coverage +* multi_buffer coverage +* consuming_buffers members and coverage +* basic_fields members and coverage +* Add string_param +* Retain ownership when reading using a message +* Fix incorrect use of [[fallthrough]] + +API Changes: + +* basic_fields refactor + +-------------------------------------------------------------------------------- + +Version 53: + +* Fix basic_parser::maybe_flatten +* Fix read_size_helper usage + +-------------------------------------------------------------------------------- + +Version 52: + +* flat_buffer is an AllocatorAwareContainer +* Add drain_buffer class + +API Changes: + +* `auto_fragment` is a member of `stream` +* `binary`, `text` are members of `stream` +* read_buffer_size is a member of `stream` +* read_message_max is a member of `stream` +* `write_buffer_size` is a member of `stream` +* `ping_callback` is a member of stream +* Remove `opcode` from `read`, `async_read` +* `read_frame` returns `bool` fin +* `opcode` is private +* finish(error_code&) is a BodyReader requirement + +Actions Required: + +* Change call sites which use `auto_fragment` with `set_option` + to call `stream::auto_fragment` instead. + +* Change call sites which use message_type with `set_option` + to call `stream::binary` or `stream::text` instead. + +* Change call sites which use `read_buffer_size` with `set_option` to + call `stream::read_buffer_size` instead. + +* Change call sites which use `read_message_max` with `set_option` to + call `stream::read_message_max` instead. + +* Change call sites which use `write_buffer_size` with `set_option` to + call `stream::write_buffer_size` instead. + +* Change call sites which use `ping_callback1 with `set_option` to + call `stream::ping_callback` instead. + +* Remove the `opcode` reference parameter from calls to synchronous + and asynchronous read functions, replace the logic with calls to + `stream::got_binary` and `stream::got_text` instead. + +* Remove the `frame_info` parameter from all read frame call sites + +* Check the return value 'fin' for calls to `read_frame` + +* Change ReadHandlers passed to `async_read_frame` to have + the signature `void(error_code, bool fin)`, use the `bool` + to indicate if the frame is the last frame. + +* Remove all occurrences of the `opcode` enum at call sites + +-------------------------------------------------------------------------------- + +Version 51 + +* Fix operator<< for header +* Tidy up file_body +* Fix file_body::get() not setting the more flag correctly +* Use BOOST_FALLTHROUGH +* Use BOOST_STRINGIZE +* DynamicBuffer benchmarks +* Add construct, destroy to handler_alloc +* Fix infinite loop in basic_parser + +API Changes: + +* Tune up static_buffer +* multi_buffer implementation change + +Actions Required: + +* Call sites passing a number to multi_buffer's constructor + will need to be adjusted, see the corresponding commit message. + +-------------------------------------------------------------------------------- + +Version 50 + +* parser is constructible from other body types +* Add field enumeration +* Use allocator more in basic_fields +* Fix basic_fields allocator awareness +* Use field in basic_fields and call sites +* Use field in basic_parser +* Tidy up basic_fields, header, and field concepts +* Fields concept work +* Body documentation work +* Add missing handler_alloc nested types +* Fix chunk delimiter parsing +* Fix test::pipe read_size +* Fix chunk header parsing + +API Changes: + +* Remove header_parser +* Add verb to on_request for parsers +* Refactor prepare +* Protect basic_fields special members +* Remove message connection settings +* Remove message free functions +* Remove obsolete serializer allocator +* http read_some, async_read_some don't return bytes + +-------------------------------------------------------------------------------- + +Version 49 + +* Use instead of + +HTTP: + +* Add HEAD request example + +API Changes: + +* Refactor method and verb +* Canonicalize string_view parameter types +* Tidy up empty_body writer error +* Refactor header status, reason, and target + +-------------------------------------------------------------------------------- + +Version 48 + +* Make buffer_prefix_view public +* Remove detail::sync_ostream +* Tidy up core type traits + +API Changes: + +* Tidy up chunk decorator +* Rename to buffer_cat_view +* Consolidate parsers to parser.hpp +* Rename to parser + +-------------------------------------------------------------------------------- + +Version 47 + +* Disable operator<< for buffer_body +* buffer_size overload for basic_multi_buffer::const_buffers_type +* Fix undefined behavior in pausation +* Fix leak in basic_flat_buffer + +API Changes: + +* Refactor treatment of request-method +* Refactor treatment of status code and obsolete reason +* Refactor HTTP serialization and parsing + +-------------------------------------------------------------------------------- + +Version 46 + +* Add test::pipe +* Documentation work + +API Changes: + +* Remove HTTP header aliases +* Refactor HTTP serialization +* Refactor type traits + +-------------------------------------------------------------------------------- + +Version 45 + +* Workaround for boost::asio::basic_streambuf type check +* Fix message doc image +* Better test::enable_yield_to +* Fix header::reason +* Documentation work +* buffer_view skips empty buffer sequences +* Disable reverse_iterator buffer_view test + +-------------------------------------------------------------------------------- + +Version 44 + +* Use BOOST_THROW_EXCEPTION +* Tidy up read_size_helper and dynamic buffers +* Require Boost 1.58.0 or later +* Tidy up and make get_lowest_layer public +* Use BOOST_STATIC_ASSERT +* Fix async return values in docs +* Fix README websocket example +* Add buffers_adapter regression test +* Tidy up is_dynamic_buffer traits test +* Make buffers_adapter meet requirements + +-------------------------------------------------------------------------------- + +Version 43 + +* Require Boost 1.64.0 +* Fix strict aliasing warnings in buffers_view +* Tidy up buffer_prefix overloads and test +* Add write limit to test::string_ostream +* Additional constructors for consuming_buffers + +-------------------------------------------------------------------------------- + +Version 42 + +* Fix javadoc typo +* Add formal review notes +* Make buffers_view a public interface + +-------------------------------------------------------------------------------- + +Version 41 + +* Trim Appveyor matrix rows +* Concept revision and documentation +* Remove coveralls integration +* Tidy up formal parameter names + +WebSocket + +* Tidy up websocket::close_code enum and constructors + +API Changes + +* Return http::error::end_of_stream on HTTP read eof +* Remove placeholders +* Rename prepare_buffer(s) to buffer_prefix +* Remove handler helpers, tidy up hook invocations + +-------------------------------------------------------------------------------- + +Version 40 + +* Add to_static_string +* Consolidate get_lowest_layer in type_traits.hpp +* Fix basic_streambuf movable trait +* Tidy up .travis.yml + +-------------------------------------------------------------------------------- + +Version 39 + +Beast versions are now identified by a single integer which +is incremented on each merge. The macro BEAST_VERSION +identifies the version number, currently at 39. A version +setting commit will always be at the tip of the master +and develop branches. + +* Use beast::string_view alias +* Fixed braced-init error with older gcc + +HTTP + +* Tidy up basic_parser javadocs + +WebSocket: + +* Add websocket async echo ssl server test: +* Fix eof error on ssl::stream shutdown + +API Changes: + +* Refactor http::header contents +* New ostream() returns dynamic buffer output stream +* New buffers() replaces to_string() +* Rename to multi_buffer, basic_multi_buffer +* Rename to flat_buffer, basic_flat_buffer +* Rename to static_buffer, static_buffer_n +* Rename to buffered_read_stream +* Harmonize concepts and identifiers with net-ts +* Tidy up HTTP reason_string + +-------------------------------------------------------------------------------- + +1.0.0-b38 + +* Refactor static_string +* Refactor base64 +* Use static_string for WebSocket handshakes +* Simplify get_lowest_layer test +* Add test_allocator to extras/test +* More flat_streambuf tests +* WebSocket doc work +* Prevent basic_fields operator[] assignment + +API Changes: + +* Refactor WebSocket error codes +* Remove websocket::keep_alive option + +-------------------------------------------------------------------------------- + +1.0.0-b37 + +* CMake hide command lines in .vcxproj Output windows" +* Rename to detail::is_invocable +* Rename project to http-bench +* Fix flat_streambuf +* Add ub sanitizer blacklist +* Add -funsigned-char to asan build target +* Fix narrowing warning in table constants + +WebSocket: + +* Add is_upgrade() free function +* Document websocket::stream thread safety +* Rename to websocket::detail::pausation + +API Changes: + +* Provide websocket::stream accept() overloads +* Refactor websocket decorators +* Move everything in basic_fields.hpp to fields.hpp +* Rename to http::dynamic_body, consolidate header + +-------------------------------------------------------------------------------- + +1.0.0-b36 + +* Update README.md + +-------------------------------------------------------------------------------- + +1.0.0-b35 + +* Add Appveyor build scripts and badge +* Tidy up MSVC CMake configuration +* Make close_code a proper enum +* Add flat_streambuf +* Rename to BEAST_DOXYGEN +* Update .gitignore for VS2017 +* Fix README.md CMake instructions + +API Changes: + +* New HTTP interfaces +* Remove http::empty_body + +-------------------------------------------------------------------------------- + 1.0.0-b34 * Fix and tidy up CMake build scripts diff --git a/CMakeLists.txt b/CMakeLists.txt index c82014589f..fa2cee17eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,19 +2,26 @@ cmake_minimum_required (VERSION 3.5.2) -project (Beast) +project (Beast VERSION 79) set_property (GLOBAL PROPERTY USE_FOLDERS ON) +option (Beast_BUILD_EXAMPLES "Build examples" ON) +option (Beast_BUILD_TESTS "Build tests" ON) if (MSVC) - # /wd4244 /wd4127 + set (CMAKE_VERBOSE_MAKEFILE FALSE) + add_definitions (-D_WIN32_WINNT=0x0601) add_definitions (-D_SCL_SECURE_NO_WARNINGS=1) add_definitions (-D_CRT_SECURE_NO_WARNINGS=1) - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4100 /wd4244 /wd4251 /MP /W4 /bigobj") - set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Ob2 /Oi /Ot /GL") - set (CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /Oi /Ot") + set (Boost_USE_STATIC_LIBS ON) + set (Boost_USE_STATIC_RUNTIME ON) + + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /W4 /bigobj /permissive-") + set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd") + set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Ob2 /Oi /Ot /GL /MT") + set (CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /Oi /Ot /MT") set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SAFESEH:NO") set (CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG") @@ -28,11 +35,15 @@ if (MSVC) set (CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO ${replacement_flags}) else() - set(THREADS_PREFER_PTHREAD_FLAG ON) - find_package(Threads) + set (THREADS_PREFER_PTHREAD_FLAG ON) + find_package (Threads) - set(CMAKE_CXX_FLAGS + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Wpedantic -Wno-unused-parameter") + + if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wrange-loop-analysis") + endif () endif() #------------------------------------------------------------------------------- @@ -42,31 +53,18 @@ endif() option (Boost_USE_STATIC_LIBS "Use static libraries for boost" ON) -set (Boost_NO_SYSTEM_PATHS ON) -set (Boost_USE_MULTITHREADED ON) +set(BOOST_COMPONENTS system) +if (Beast_BUILD_EXAMPLES OR Beast_BUILD_TESTS) + list(APPEND BOOST_COMPONENTS coroutine context filesystem program_options thread) +endif() +find_package (Boost 1.58.0 REQUIRED COMPONENTS ${BOOST_COMPONENTS}) -unset (Boost_INCLUDE_DIR CACHE) -unset (Boost_LIBRARY_DIRS CACHE) -find_package (Boost REQUIRED COMPONENTS - coroutine - context - filesystem - program_options - system - thread - ) +link_directories(${Boost_LIBRARY_DIRS}) -include_directories (SYSTEM ${Boost_INCLUDE_DIRS}) -link_libraries (${Boost_LIBRARIES}) - -if (MSVC) - add_definitions (-DBOOST_ALL_NO_LIB) # disable autolinking -elseif (MINGW) +if (MINGW) link_libraries(ws2_32 mswsock) endif() -add_definitions (-DBOOST_COROUTINES_NO_DEPRECATION_WARNING=1) # for asio - #------------------------------------------------------------------------------- # # OpenSSL @@ -83,15 +81,23 @@ endif() find_package(OpenSSL) +if (OPENSSL_FOUND) + add_definitions (-DBEAST_USE_OPENSSL=1) + +else() + add_definitions (-DBEAST_USE_OPENSSL=0) + message("OpenSSL not found.") +endif() + # #------------------------------------------------------------------------------- function(DoGroupSources curdir rootdir folder) - file(GLOB children RELATIVE ${PROJECT_SOURCE_DIR}/${curdir} ${PROJECT_SOURCE_DIR}/${curdir}/*) - foreach(child ${children}) - if(IS_DIRECTORY ${PROJECT_SOURCE_DIR}/${curdir}/${child}) + file (GLOB children RELATIVE ${PROJECT_SOURCE_DIR}/${curdir} ${PROJECT_SOURCE_DIR}/${curdir}/*) + foreach (child ${children}) + if (IS_DIRECTORY ${PROJECT_SOURCE_DIR}/${curdir}/${child}) DoGroupSources(${curdir}/${child} ${rootdir} ${folder}) - elseif(${child} STREQUAL "CMakeLists.txt") + elseif (${child} STREQUAL "CMakeLists.txt") source_group("" FILES ${PROJECT_SOURCE_DIR}/${curdir}/${child}) else() string(REGEX REPLACE ^${rootdir} ${folder} groupname ${curdir}) @@ -102,77 +108,87 @@ function(DoGroupSources curdir rootdir folder) endfunction() function(GroupSources curdir folder) - DoGroupSources(${curdir} ${curdir} ${folder}) + DoGroupSources (${curdir} ${curdir} ${folder}) endfunction() #------------------------------------------------------------------------------- if ("${VARIANT}" STREQUAL "coverage") - set(CMAKE_CXX_FLAGS - "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") - set(CMAKE_BUILD_TYPE RELWITHDEBINFO) - set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lgcov") -elseif ("${VARIANT}" STREQUAL "asan") - set(CMAKE_CXX_FLAGS - "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") - set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") - set(CMAKE_BUILD_TYPE RELWITHDEBINFO) -elseif ("${VARIANT}" STREQUAL "usan") - set(CMAKE_CXX_FLAGS - "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fno-omit-frame-pointer") - set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined") - set(CMAKE_BUILD_TYPE RELWITHDEBINFO) + if (MSVC) + else() + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.2 -fprofile-arcs -ftest-coverage") + set (CMAKE_BUILD_TYPE RELWITHDEBINFO) + set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lgcov") + endif() + +elseif ("${VARIANT}" STREQUAL "ubasan") + if (MSVC) + else() + set (CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} -DBEAST_NO_SLOW_TESTS=1 -msse4.2 -funsigned-char -fno-omit-frame-pointer -fsanitize=address,undefined -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/scripts/blacklist.supp") + set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address,undefined") + set (CMAKE_BUILD_TYPE RELWITHDEBINFO) + endif() + elseif ("${VARIANT}" STREQUAL "debug") - set(CMAKE_BUILD_TYPE DEBUG) + set (CMAKE_BUILD_TYPE DEBUG) + elseif ("${VARIANT}" STREQUAL "release") - set(CMAKE_BUILD_TYPE RELEASE) + set (CMAKE_BUILD_TYPE RELEASE) + endif() +#------------------------------------------------------------------------------- +# +# Library interface +# + +add_library (${PROJECT_NAME} INTERFACE) +target_link_libraries (${PROJECT_NAME} INTERFACE ${Boost_SYSTEM_LIBRARY}) +if (NOT MSVC) + target_link_libraries (${PROJECT_NAME} INTERFACE Threads::Threads) +endif() +target_compile_definitions (${PROJECT_NAME} INTERFACE BOOST_COROUTINES_NO_DEPRECATION_WARNING=1) +target_include_directories(${PROJECT_NAME} INTERFACE ${PROJECT_SOURCE_DIR}/include) +target_include_directories(${PROJECT_NAME} SYSTEM INTERFACE ${Boost_INCLUDE_DIRS}) + +#------------------------------------------------------------------------------- +# +# Tests and examples +# + +include_directories (.) include_directories (extras) include_directories (include) -set(ZLIB_SOURCES - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/crc32.h - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/deflate.h - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inffast.h - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inffixed.h - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inflate.h - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inftrees.h - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/trees.h - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/zlib.h - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/zutil.h - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/adler32.c - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/compress.c - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/crc32.c - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/deflate.c - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/infback.c - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inffast.c - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inflate.c - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inftrees.c - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/trees.c - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/uncompr.c - ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/zutil.c -) +if (OPENSSL_FOUND) + include_directories (${OPENSSL_INCLUDE_DIR}) +endif() file(GLOB_RECURSE BEAST_INCLUDES ${PROJECT_SOURCE_DIR}/include/beast/*.hpp ${PROJECT_SOURCE_DIR}/include/beast/*.ipp ) +file(GLOB_RECURSE COMMON_INCLUDES + ${PROJECT_SOURCE_DIR}/example/common/*.hpp + ) + +file(GLOB_RECURSE EXAMPLE_INCLUDES + ${PROJECT_SOURCE_DIR}/example/*.hpp + ) + file(GLOB_RECURSE EXTRAS_INCLUDES ${PROJECT_SOURCE_DIR}/extras/beast/*.hpp ${PROJECT_SOURCE_DIR}/extras/beast/*.ipp ) -add_subdirectory (examples) -if (NOT OPENSSL_FOUND) - message("OpenSSL not found. Not building examples/ssl") -else() - add_subdirectory (examples/ssl) +if (Beast_BUILD_TESTS) + add_subdirectory (test) endif() -add_subdirectory (test) -add_subdirectory (test/core) -add_subdirectory (test/http) -add_subdirectory (test/websocket) -add_subdirectory (test/zlib) +if (Beast_BUILD_EXAMPLES AND + (NOT "${VARIANT}" STREQUAL "coverage") AND + (NOT "${VARIANT}" STREQUAL "ubasan")) + add_subdirectory (example) +endif() diff --git a/Jamroot b/Jamroot index 277100ab23..a35ffbb5de 100644 --- a/Jamroot +++ b/Jamroot @@ -8,6 +8,8 @@ import os ; import feature ; import boost ; +import modules ; +import testing ; boost.use-project ; @@ -50,40 +52,24 @@ if [ os.name ] = MACOSX using clang : : ; } -variant coverage - : - debug - : - "-fprofile-arcs -ftest-coverage" +variant coverage : + release + : + "-msse4.2 -fprofile-arcs -ftest-coverage" "-lgcov" - ; + ; -variant asan +variant ubasan : release : - "-fsanitize=address -fno-omit-frame-pointer" - "-fsanitize=address" - ; - -variant msan - : - debug - : - "-fsanitize=memory -fno-omit-frame-pointer -fsanitize-memory-track-origins=2 -fsanitize-memory-use-after-dtor" - "-fsanitize=memory" - ; - -variant usan - : - debug - : - "-fsanitize=undefined -fno-omit-frame-pointer" - "-fsanitize=undefined" + "-msse4.2 -funsigned-char -fno-omit-frame-pointer -fsanitize=address,undefined -fsanitize-blacklist=scripts/blacklist.supp" + "-fsanitize=address,undefined" ; project beast : requirements + /boost//headers . ./extras ./include @@ -103,9 +89,10 @@ project beast clang:-std=c++11 clang:-Wno-unused-parameter clang:-Wno-unused-variable # Temporary until we can figure out -isystem + clang:-Wrange-loop-analysis msvc:_SCL_SECURE_NO_WARNINGS=1 msvc:_CRT_SECURE_NO_WARNINGS=1 - msvc:"/wd4100 /wd4251 /bigobj" + msvc:"/permissive- /bigobj" msvc:release:"/Ob2 /Oi /Ot" LINUX:_XOPEN_SOURCE=600 LINUX:_GNU_SOURCE=1 @@ -125,4 +112,4 @@ project beast ; build-project test ; -build-project examples ; +build-project example ; diff --git a/README.md b/README.md index 1dcf4b52a1..9b854e8a6c 100644 --- a/README.md +++ b/README.md @@ -1,63 +1,50 @@ Beast -[![Join the chat at https://gitter.im/vinniefalco/Beast](https://badges.gitter.im/vinniefalco/Beast.svg)](https://gitter.im/vinniefalco/Beast?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/vinniefalco/Beast.svg?branch=master)](https://travis-ci.org/vinniefalco/Beast) [![codecov](https://codecov.io/gh/vinniefalco/Beast/branch/master/graph/badge.svg)](https://codecov.io/gh/vinniefalco/Beast) [![coveralls](https://coveralls.io/repos/github/vinniefalco/Beast/badge.svg?branch=master)](https://coveralls.io/github/vinniefalco/Beast?branch=master) [![Documentation](https://img.shields.io/badge/documentation-master-brightgreen.svg)](http://vinniefalco.github.io/beast/) [![License](https://img.shields.io/badge/license-boost-brightgreen.svg)](LICENSE_1_0.txt) - # HTTP and WebSocket built on Boost.Asio in C++11 ---- - -## Appearances - -| CppCast 2017 | CppCon 2016 | -| ------------ | ----------- | -| Vinnie Falco | Beast | - ---- +Branch | Build | Coverage | Documentation +------------|---------------|----------------|--------------- +[master](https://github.com/vinniefalco/Beast/tree/master) | [![Build Status](https://travis-ci.org/vinniefalco/Beast.svg?branch=master)](https://travis-ci.org/vinniefalco/Beast) [![Build status](https://ci.appveyor.com/api/projects/status/g0llpbvhpjuxjnlw/branch/master?svg=true)](https://ci.appveyor.com/project/vinniefalco/beast/branch/master) | [![codecov](https://codecov.io/gh/vinniefalco/Beast/branch/master/graph/badge.svg)](https://codecov.io/gh/vinniefalco/Beast/branch/master) | [![Documentation](https://img.shields.io/badge/documentation-master-brightgreen.svg)](http://vinniefalco.github.io/beast/) +[develop](https://github.com/vinniefalco/Beast/tree/develop) | [![Build Status](https://travis-ci.org/vinniefalco/Beast.svg?branch=develop)](https://travis-ci.org/vinniefalco/Beast) [![Build status](https://ci.appveyor.com/api/projects/status/g0llpbvhpjuxjnlw/branch/develop?svg=true)](https://ci.appveyor.com/project/vinniefalco/beast/branch/develop) | [![codecov](https://codecov.io/gh/vinniefalco/Beast/branch/develop/graph/badge.svg)](https://codecov.io/gh/vinniefalco/Beast/branch/develop) | [![Documentation](https://img.shields.io/badge/documentation-develop-brightgreen.svg)](http://vinniefalco.github.io/stage/beast/develop) ## Contents - [Introduction](#introduction) +- [Appearances](#appearances) - [Description](#description) - [Requirements](#requirements) - [Building](#building) - [Usage](#usage) - [Licence](#licence) - [Contact](#contact) +- [Contributing](#Contributing) ## Introduction -Beast is a header-only, cross-platform C++ library built on Boost.Asio and -Boost, containing two modules implementing widely used network protocols. -Beast.HTTP offers a universal model for describing, sending, and receiving -HTTP messages while Beast.WebSocket provides a complete implementation of -the WebSocket protocol. Their design achieves these goals: +Beast is a C++ header-only library serving as a foundation for writing +interoperable networking libraries by providing **low-level HTTP/1, +WebSocket, and networking protocol** vocabulary types and algorithms +using the consistent asynchronous model of Boost.Asio. -* **Symmetry.** Interfaces are role-agnostic; the same interfaces can be -used to build clients, servers, or both. +This library is designed for: -* **Ease of Use.** HTTP messages are modeled using simple, readily -accessible objects. Functions and classes used to send and receive HTTP -or WebSocket messages are designed to resemble Boost.Asio as closely as -possible. Users familiar with Boost.Asio will be immediately comfortable -using this library. +* **Symmetry:** Algorithms are role-agnostic; build clients, servers, or both. -* **Flexibility.** Interfaces do not mandate specific implementation -strategies; important decisions such as buffer or thread management are -left to users of the library. +* **Ease of Use:** Boost.Asio users will immediately understand Beast. -* **Performance.** The implementation performs competitively, making it a -realistic choice for building high performance network servers. +* **Flexibility:** Users make the important decisions such as buffer or + thread management. -* **Scalability.** Development of network applications that scale to thousands -of concurrent connections is possible with the implementation. +* **Performance:** Build applications handling thousands of connections or more. -* **Basis for further abstraction.** The interfaces facilitate the -development of other libraries that provide higher levels of abstraction. +* **Basis for Further Abstraction.** Components are well-suited for building upon. -Beast is used in [rippled](https://github.com/ripple/rippled), an -open source server application that implements a decentralized -cryptocurrency system. +## Appearances + +| CppCast 2017 | CppCon 2016 | +| ------------ | ----------- | +| Vinnie Falco | Beast | ## Description @@ -73,17 +60,20 @@ The library has been submitted to the ## Requirements -* Boost 1.58 or later -* C++11 or later +This library is for programmers familiar with Boost.Asio. Users +who wish to use asynchronous interfaces should already know how to +create concurrent network programs using callbacks or coroutines. + +* **C++11:** Robust support for most language features. +* **Boost:** Boost.Asio and some other parts of Boost. +* **OpenSSL:** Optional, for using TLS/Secure sockets. When using Microsoft Visual C++, Visual Studio 2015 Update 3 or later is required. -These components are optionally required in order to build the -tests and examples: +These components are required in order to build the tests and examples: -* OpenSSL (optional) -* CMake 3.7.2 or later (optional) -* Properly configured bjam/b2 (optional) +* CMake 3.7.2 or later +* Properly configured bjam/b2 ## Building @@ -106,24 +96,21 @@ instructions on how to do this for your particular build system. For the examples and tests, Beast provides build scripts for Boost.Build (bjam) and CMake. It is possible to generate Microsoft Visual Studio or Apple -Developers using Microsoft Visual Studio can generate Visual Studio -project files by executing these commands from the root of the repository: +Xcode project files using CMake by executing these commands from +the root of the repository: ``` +mkdir bin cd bin cmake .. # for 32-bit Windows builds - -cd ../bin64 -cmake .. # for Linux/Mac builds, OR -cmake -G"Visual Studio 14 2015 Win64" .. # for 64-bit Windows builds -``` - -When using Apple Xcode it is possible to generate Xcode project files -using these commands: - -``` -cd bin cmake -G Xcode .. # for Apple Xcode builds + +cd .. +mkdir bin64 +cd bin64 +cmake -G"Visual Studio 14 2015 Win64" .. # for 64-bit Windows builds (VS2015) +cmake -G"Visual Studio 15 2017 Win64" .. # for 64-bit Windows builds (VS2017) + ``` To build with Boost.Build, it is necessary to have the bjam executable @@ -141,13 +128,13 @@ The files in the repository are laid out thusly: ``` ./ - bin/ Holds executables and project files - bin64/ Holds 64-bit Windows executables and project files + bin/ Create this to hold executables and project files + bin64/ Create this to hold 64-bit Windows executables and project files doc/ Source code and scripts for the documentation include/ Add this to your compiler includes beast/ extras/ Additional APIs, may change - examples/ Self contained example programs + example/ Self contained example programs test/ Unit tests and benchmarks ``` @@ -155,76 +142,9 @@ The files in the repository are laid out thusly: ## Usage These examples are complete, self-contained programs that you can build -and run yourself (they are in the `examples` directory). +and run yourself (they are in the `example` directory). -Example WebSocket program: -```C++ -#include -#include -#include -#include -#include - -int main() -{ - // Normal boost::asio setup - std::string const host = "echo.websocket.org"; - boost::asio::io_service ios; - boost::asio::ip::tcp::resolver r{ios}; - boost::asio::ip::tcp::socket sock{ios}; - boost::asio::connect(sock, - r.resolve(boost::asio::ip::tcp::resolver::query{host, "80"})); - - // WebSocket connect and send message using beast - beast::websocket::stream ws{sock}; - ws.handshake(host, "/"); - ws.write(boost::asio::buffer(std::string("Hello, world!"))); - - // Receive WebSocket message, print and close using beast - beast::streambuf sb; - beast::websocket::opcode op; - ws.read(op, sb); - ws.close(beast::websocket::close_code::normal); - std::cout << beast::to_string(sb.data()) << "\n"; -} -``` - -Example HTTP program: -```C++ -#include -#include -#include -#include -#include - -int main() -{ - // Normal boost::asio setup - std::string const host = "boost.org"; - boost::asio::io_service ios; - boost::asio::ip::tcp::resolver r{ios}; - boost::asio::ip::tcp::socket sock{ios}; - boost::asio::connect(sock, - r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"})); - - // Send HTTP request using beast - beast::http::request req; - req.method = "GET"; - req.url = "/"; - req.version = 11; - req.fields.replace("Host", host + ":" + - boost::lexical_cast(sock.remote_endpoint().port())); - req.fields.replace("User-Agent", "Beast"); - beast::http::prepare(req); - beast::http::write(sock, req); - - // Receive and print HTTP response using beast - beast::streambuf sb; - beast::http::response resp; - beast::http::read(sock, sb, resp); - std::cout << resp; -} -``` +http://vinniefalco.github.io/beast/beast/quick_start.html ## License @@ -236,3 +156,51 @@ http://www.boost.org/LICENSE_1_0.txt) Please report issues or questions here: https://github.com/vinniefalco/Beast/issues + + +--- + +## Contributing (We Need Your Help!) + +If you would like to contribute to Beast and help us maintain high +quality, consider performing code reviews on active pull requests. +Any feedback from users and stakeholders, even simple questions about +how things work or why they were done a certain way, carries value +and can be used to improve the library. Code review provides these +benefits: + +* Identify bugs +* Documentation proof-reading +* Adjust interfaces to suit use-cases +* Simplify code + +You can look through the Closed pull requests to get an idea of how +reviews are performed. To give a code review just sign in with your +GitHub account and then add comments to any open pull requests below, +don't be shy! +

https://github.com/vinniefalco/Beast/pulls

+ +Here are some resources to learn more about +code reviews: + +* Top 10 Pull Request Review Mistakes +* Best Kept Secrets of Peer Code Review (pdf) +* 11 Best Practices for Peer Code Review (pdf) +* Code Review Checklist – To Perform Effective Code Reviews +* Code review guidelines +* C++ Core Guidelines +* C++ Coding Standards (Sutter & Andrescu) + +Beast thrives on code reviews and any sort of feedback from users and +stakeholders about its interfaces. Even if you just have questions, +asking them in the code review or in issues provides valuable information +that can be used to improve the library - do not hesitate, no question +is insignificant or unimportant! + +While code reviews are the preferred form of donation, if you simply +must donate money to support the library, please do so +using Bitcoin sent to this address: +1DaPsDvv6MjFUSnsxXSHzeYKSjzrWrQY7T + + + diff --git a/TODO.txt b/TODO.txt deleted file mode 100644 index 13de4b341b..0000000000 --- a/TODO.txt +++ /dev/null @@ -1,58 +0,0 @@ -* Add writer::prepare(msg&) interface to set Content-Type - -Boost.Http -* Use enum instead of bool in isRequest - -Docs: -* Include Example program listings in the docs -* Fix index in docs -* melpon sandbox? -* Implement cleanup-param to remove spaces around template arguments - e.g. in basic_streambuf move constructor members -* Don't put using namespace at file scope in examples, - do something like "using ba = boost::asio" instead. - -Core: -* Replace Jamroot with Jamfile -* Fix bidirectional buffers iterators operator->() -* Complete allocator testing in basic_streambuf - -WebSocket: -* Minimize sizeof(websocket::stream) -* Move check for message size limit to account for compression -* more invokable unit test coverage -* More control over the HTTP request and response during handshakes -* optimized versions of key/masking, choose prepared_key size -* Give callers control over the http request/response used during handshake -* Investigate poor autobahn results in Debug builds -* Fall through composed operation switch cases -* Use close_code::no_code instead of close_code::none -* Make request_type, response_type public APIs, - use in stream member function signatures - -HTTP: -* Define Parser concept in HTTP - - Need parse version of read() so caller can set parser options - like maximum size of headers, maximum body size, etc -* add bool should_close(message_v1 const&) to replace the use - of eof return value from write and async_write -* More fine grained parser errors -* HTTP parser size limit with test (configurable?) -* HTTP parser trailers with test -* Decode chunk encoding parameters -* URL parser, strong URL character checking in HTTP parser -* Fix prepare() calling content_length() without init() -* Complete allocator testing in basic_streambuf, basic_headers -* Custom HTTP error codes for various situations -* Branch prediction hints in parser -* Check basic_parser_v1 against rfc7230 for leading message whitespace -* Fix the order of message constructor parameters: - body first then headers (since body is constructed with arguments more often) -* Unit tests for char tables -* Remove status_code() from API when isRequest==true, et. al. -* Permit sending trailers and parameters in chunk-encoding chunks - -Future: - -* SOCKS proxy client and server implementations - diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000000..e6f93604fc --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,102 @@ +# Copyright 2016 Peter Dimov +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at http://boost.org/LICENSE_1_0.txt) + +#version: 1.0.{build}-{branch} +version: "{branch} (#{build})" + +shallow_clone: true + +platform: + #- x86 + - x64 + +configuration: + #- Debug + - Release + +install: + - cd .. + - git clone https://github.com/boostorg/boost.git boost + - cd boost +# - git checkout boost-1.64.0 + - xcopy /s /e /q %APPVEYOR_BUILD_FOLDER% libs\beast\ + - git submodule update --init tools/build + - git submodule update --init libs/config + - git submodule update --init tools/boostdep +# - python tools/boostdep/depinst/depinst.py beast + - git submodule update --init libs/any + - git submodule update --init libs/asio + - git submodule update --init libs/algorithm + - git submodule update --init libs/array + - git submodule update --init libs/assert + - git submodule update --init libs/atomic + - git submodule update --init libs/bind + - git submodule update --init libs/chrono + - git submodule update --init libs/concept_check + - git submodule update --init libs/config + - git submodule update --init libs/container + - git submodule update --init libs/context + - git submodule update --init libs/conversion + - git submodule update --init libs/core + - git submodule update --init libs/coroutine + - git submodule update --init libs/date_time + - git submodule update --init libs/detail + - git submodule update --init libs/endian + - git submodule update --init libs/exception + - git submodule update --init libs/filesystem + - git submodule update --init libs/foreach + - git submodule update --init libs/function + - git submodule update --init libs/function_types + - git submodule update --init libs/functional + - git submodule update --init libs/fusion + - git submodule update --init libs/integer + - git submodule update --init libs/intrusive + - git submodule update --init libs/io + - git submodule update --init libs/iostreams + - git submodule update --init libs/iterator + - git submodule update --init libs/lambda + - git submodule update --init libs/lexical_cast + - git submodule update --init libs/locale + - git submodule update --init libs/logic + - git submodule update --init libs/math + - git submodule update --init libs/move + - git submodule update --init libs/mpl + - git submodule update --init libs/numeric/conversion + - git submodule update --init libs/optional +# - git submodule update --init libs/phoenix + - git submodule update --init libs/pool + - git submodule update --init libs/predef + - git submodule update --init libs/preprocessor + - git submodule update --init libs/program_options + - git submodule update --init libs/proto + - git submodule update --init libs/random + - git submodule update --init libs/range + - git submodule update --init libs/ratio + - git submodule update --init libs/rational + - git submodule update --init libs/regex + - git submodule update --init libs/serialization + - git submodule update --init libs/smart_ptr +# - git submodule update --init libs/spirit + - git submodule update --init libs/static_assert + - git submodule update --init libs/system + - git submodule update --init libs/thread + - git submodule update --init libs/throw_exception + - git submodule update --init libs/tokenizer + - git submodule update --init libs/tti + - git submodule update --init libs/tuple + - git submodule update --init libs/type_index + - git submodule update --init libs/type_traits + - git submodule update --init libs/typeof + - git submodule update --init libs/unordered + - git submodule update --init libs/utility + - git submodule update --init libs/variant + - git submodule update --init libs/winapi + - bootstrap + - b2 headers + +build: off + +test_script: + - b2 libs/beast/example toolset=msvc-14.0 + - b2 libs/beast/test toolset=msvc-14.0 diff --git a/doc/0_main.qbk b/doc/0_main.qbk new file mode 100644 index 0000000000..90de1b15bb --- /dev/null +++ b/doc/0_main.qbk @@ -0,0 +1,113 @@ +[/ + 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) +] + +[library Beast + [quickbook 1.6] + [copyright 2013 - 2017 Vinnie Falco] + [purpose Networking Protocol Library] + [license + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + [@http://www.boost.org/LICENSE_1_0.txt]) + ] + [authors [Falco, Vinnie]] + [category template] + [category generic] +] + +[template mdash[] '''— '''] +[template indexterm1[term1] ''''''[term1]''''''] +[template indexterm2[term1 term2] ''''''[term1]''''''[term2]''''''] +[template repo_file[path] ''''''[path]''''''] +[template include_file[path][^<''''''[path]''''''>]] + +[def __N3747__ [@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3747.pdf [*N3747]]] +[def __N4588__ [@http://cplusplus.github.io/networking-ts/draft.pdf [*N4588]]] +[def __rfc6455__ [@https://tools.ietf.org/html/rfc6455 rfc6455]] +[def __rfc7230__ [@https://tools.ietf.org/html/rfc7230 rfc7230]] + +[def __Asio__ [@http://www.boost.org/doc/html/boost_asio.html Boost.Asio]] + +[def __asio_handler_invoke__ [@http://www.boost.org/doc/html/boost_asio/reference/asio_handler_invoke.html `asio_handler_invoke`]] +[def __asio_handler_allocate__ [@http://www.boost.org/doc/html/boost_asio/reference/asio_handler_allocate.html `asio_handler_allocate`]] +[def __io_service__ [@http://www.boost.org/doc/html/boost_asio/reference/io_service.html `io_service`]] +[def __socket__ [@http://www.boost.org/doc/html/boost_asio/reference/ip__tcp/socket.html `boost::asio::ip::tcp::socket`]] +[def __ssl_stream__ [@http://www.boost.org/doc/html/boost_asio/reference/ssl__stream.html `boost::asio::ssl::stream`]] +[def __streambuf__ [@http://www.boost.org/doc/html/boost_asio/reference/streambuf.html `boost::asio::streambuf`]] +[def __use_future__ [@http://www.boost.org/doc/html/boost_asio/reference/use_future_t.html `boost::asio::use_future`]] +[def __void_or_deduced__ [@http://www.boost.org/doc/html/boost_asio/reference/asynchronous_operations.html#boost_asio.reference.asynchronous_operations.return_type_of_an_initiating_function ['void-or-deduced]]] +[def __yield_context__ [@http://www.boost.org/doc/html/boost_asio/reference/yield_context.html `boost::asio::yield_context`]] + +[def __AsyncReadStream__ [@http://www.boost.org/doc/html/boost_asio/reference/AsyncReadStream.html [*AsyncReadStream]]] +[def __AsyncWriteStream__ [@http://www.boost.org/doc/html/boost_asio/reference/AsyncWriteStream.html [*AsyncWriteStream]]] +[def __CompletionHandler__ [@http://www.boost.org/doc/html/boost_asio/reference/CompletionHandler.html [*CompletionHandler]]] +[def __ConstBufferSequence__ [@http://www.boost.org/doc/html/boost_asio/reference/ConstBufferSequence.html [*ConstBufferSequence]]] +[def __Handler__ [@http://www.boost.org/doc/html/boost_asio/reference/Handler.html [*Handler]]] +[def __MutableBufferSequence__ [@http://www.boost.org/doc/html/boost_asio/reference/MutableBufferSequence.html [*MutableBufferSequence]]] +[def __SyncReadStream__ [@http://www.boost.org/doc/html/boost_asio/reference/SyncReadStream.html [*SyncReadStream]]] +[def __SyncWriteStream__ [@http://www.boost.org/doc/html/boost_asio/reference/SyncWriteStream.html [*SyncWriteStream]]] + +[def __async_initfn__ [@http://www.boost.org/doc/html/boost_asio/reference/asynchronous_operations.html initiating function]] + +[def __AsyncStream__ [link beast.concept.streams.AsyncStream [*AsyncStream]]] +[def __Body__ [link beast.concept.Body [*Body]]] +[def __BodyReader__ [link beast.concept.BodyReader [*BodyReader]]] +[def __BodyWriter__ [link beast.concept.BodyWriter [*BodyWriter]]] +[def __DynamicBuffer__ [link beast.concept.DynamicBuffer [*DynamicBuffer]]] +[def __Fields__ [link beast.concept.Fields [*Fields]]] +[def __FieldsReader__ [link beast.concept.FieldsReader [*FieldsReader]]] +[def __File__ [link beast.concept.File [*File]]] +[def __Stream__ [link beast.concept.streams [*Stream]]] +[def __SyncStream__ [link beast.concept.streams.SyncStream [*SyncStream]]] + +[def __basic_fields__ [link beast.ref.beast__http__basic_fields `basic_fields`]] +[def __basic_multi_buffer__ [link beast.ref.beast__basic_multi_buffer `basic_multi_buffer`]] +[def __basic_parser__ [link beast.ref.beast__http__basic_parser `basic_parser`]] +[def __buffer_body__ [link beast.ref.beast__http__buffer_body `buffer_body`]] +[def __fields__ [link beast.ref.beast__http__fields `fields`]] +[def __flat_buffer__ [link beast.ref.beast__flat_buffer `flat_buffer`]] +[def __header__ [link beast.ref.beast__http__header `header`]] +[def __message__ [link beast.ref.beast__http__message `message`]] +[def __multi_buffer__ [link beast.ref.beast__multi_buffer `multi_buffer`]] +[def __parser__ [link beast.ref.beast__http__parser `parser`]] +[def __serializer__ [link beast.ref.beast__http__serializer `serializer`]] +[def __static_buffer__ [link beast.ref.beast__static_buffer `static_buffer`]] +[def __static_buffer_n__ [link beast.ref.beast__static_buffer_n `static_buffer_n`]] + +[import ../example/common/detect_ssl.hpp] +[import ../example/doc/http_examples.hpp] +[import ../example/echo-op/echo_op.cpp] +[import ../example/http-client/http_client.cpp] +[import ../example/websocket-client/websocket_client.cpp] + +[import ../include/beast/http/file_body.hpp] + +[import ../test/exemplars.cpp] +[import ../test/core/doc_snippets.cpp] +[import ../test/http/doc_snippets.cpp] +[import ../test/websocket/doc_snippets.cpp] + +[include 1_intro.qbk] +[include 2_examples.qbk] +[include 3_0_core.qbk] +[include 5_00_http.qbk] +[include 6_0_http_examples.qbk] +[include 7_0_websocket.qbk] +[include 8_concepts.qbk] +[include 9_0_design.qbk] + +[section:quickref Reference] +[xinclude quickref.xml] +[endsect] + +[block'''This Page Intentionally Left Blank 1/2'''] +[section:ref This Page Intentionally Left Blank 2/2] +[include reference.qbk] +[endsect] +[block''''''] + +[xinclude index.xml] diff --git a/doc/1_intro.qbk b/doc/1_intro.qbk new file mode 100644 index 0000000000..3927ebddb5 --- /dev/null +++ b/doc/1_intro.qbk @@ -0,0 +1,96 @@ +[/ + 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) +] + +[section:intro Introduction] + +Beast is a C++ header-only library serving as a foundation for writing +interoperable networking libraries by providing [*low-level HTTP/1, +WebSocket, and networking protocol] vocabulary types and algorithms +using the consistent asynchronous model of __Asio__. + +This library is designed for: + +* [*Symmetry:] Algorithms are role-agnostic; build clients, servers, or both. + +* [*Ease of Use:] __Asio__ users will immediately understand Beast. + +* [*Flexibility:] Users make the important decisions such as buffer or + thread management. + +* [*Performance:] Build applications handling thousands of connections or more. + +* [*Basis for Further Abstraction.] Components are well-suited for building upon. + +Beast is not an HTTP client or HTTP server, but it can be used to build +those things. + +[heading Motivation] + +Beast empowers users to create their own libraries, clients, and servers +using HTTP/1 and WebSocket. Code will be easier and faster to implement, +understand, and maintain, because Beast takes care of the low-level +protocol details. +The HTTP and WebSocket protocols drive most of the World Wide Web. +Every web browser implements these protocols to load webpages and +to enable client side programs (often written in JavaScript) to +communicate interactively. C++ benefits greatly from having a +standardized implementation of these protocols. + +[heading Requirements] + +[important + This library is for programmers familiar with __Asio__. Users who + wish to use asynchronous interfaces should already know how to + create concurrent network programs using callbacks or coroutines. +] + +Beast requires: + +* [*C++11:] Robust support for most language features. +* [*Boost:] Beast only works with Boost, not stand-alone Asio +* [*OpenSSL:] Optional, for using TLS/Secure sockets. + +Supported compilers: msvc-14+, gcc 4.8+, clang 3.6+ + +Sources are [*header-only]. To link a program using Beast successfully, add the +[@http://www.boost.org/libs/system/doc/reference.html Boost.System] +library to the list of linked libraries. If you use coroutines +you'll also need the +[@http://www.boost.org/libs/coroutine/doc/html/index.html Boost.Coroutine] +library. Please visit the +[@http://www.boost.org/doc/ Boost documentation] +for instructions on how to do this for your particular build system. + +[heading Credits] + +Boost.Asio is the inspiration behind which all of the interfaces and +implementation strategies are built. Some parts of the documentation are +written to closely resemble the wording and presentation of Boost.Asio +documentation. Credit goes to +[@https://github.com/chriskohlhoff Christopher Kohlhoff] +for his wonderful Asio library and the ideas in __N4588__ which power Beast. + +Beast would not be possible without the support of +[@https://www.ripple.com Ripple] +during the library's early development, or the ideas, time and patience +contributed by +[@https://github.com/JoelKatz David Schwartz], +[@https://github.com/ximinez Edward Hennis], +[@https://github.com/howardhinnant Howard Hinnant], +[@https://github.com/miguelportilla Miguel Portilla], +[@https://github.com/nbougalis Nik Bougalis], +[@https://github.com/seelabs Scott Determan], +[@https://github.com/scottschurr Scott Schurr], +Many thanks to +[@https://github.com/K-ballo Agustín Bergé], +[@http://www.boost.org/users/people/glen_fernandes.html Glen Fernandes], +and +[@https://github.com/pdimov Peter Dimov] +for tirelessly answering questions on +[@https://cpplang.slack.com/ Cpplang-Slack]. + +[endsect] diff --git a/doc/2_examples.qbk b/doc/2_examples.qbk new file mode 100644 index 0000000000..31cd55650e --- /dev/null +++ b/doc/2_examples.qbk @@ -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) +] + +[section:quickstart Quick Start] +[block''''''] + +These complete programs are intended to quickly impress upon readers +the flavor of the library. Source code and build scripts for them are +located in the example/ directory. + +[section HTTP Client] + +Use HTTP to make a GET request to a website and print the response: + +File: [repo_file example/http-client/http_client.cpp] + +[example_http_client] + +[endsect] + +[section WebSocket Client] + +Establish a WebSocket connection, send a message and receive the reply: + +File: [repo_file example/websocket-client/websocket_client.cpp] + +[example_websocket_client] + +[endsect] + +[endsect] + + + +[section:examples Examples] +[block''''''] + +Source code and build scripts for these programs are located +in the example/ directory. + + + +[section HTTP Crawl] + +This example retrieves the page at each of the most popular domains +as measured by Alexa. + +* [repo_file example/http-crawl/http_crawl.cpp] + +[endsect] + + + +[section HTTP Client (with SSL)] + +This example demonstrates sending and receiving HTTP messages +over a TLS connection. Requires OpenSSL to build. + +* [repo_file example/http-client-ssl/http_client_ssl.cpp] + +[endsect] + + + +[section HTTP Server (Fast)] + +This example implements a very simple HTTP server with +some optimizations suitable for calculating benchmarks. + +* [repo_file example/http-server-fast/fields_alloc.hpp] +* [repo_file example/http-server-fast/http_server_fast.cpp] + +[endsect] + + + +[section HTTP Server (Small)] + +This example implements a very simple HTTP server +suitable as a starting point on an embedded device. + +* [repo_file example/http-server-small/http_server_small.cpp] + +[endsect] + + + +[section HTTP Server (Threaded)] + +This example implements a very simple HTTP server using +synchronous interfaces and using one thread per connection: + +* [repo_file example/http-server-threaded/http_server_threaded.cpp] + +[endsect] + + + +[section WebSocket Client (with SSL)] + +Establish a WebSocket connection over an encrypted TLS connection, +send a message and receive the reply. Requires OpenSSL to build. + +* [repo_file example/websocket-client-ssl/websocket_client_ssl.cpp] + +[endsect] + + + +[section WebSocket Server (Asynchronous)] + +This program implements a WebSocket echo server using asynchronous +interfaces and a configurable number of threads. + +* [repo_file example/websocket-server-async/websocket_server_async.cpp] + +[endsect] + + + +[section Documentation Samples] + +Here are all of the example functions and classes presented +throughout the documentation, they can be included and used +in your program without modification + +* [repo_file example/doc/http_examples.hpp] + +[endsect] + + + +[section Composed Operations] + +This program shows how to use Beast's network foundations to build a +composable asynchronous initiation function with associated composed +operation implementation. This is a complete, runnable version of +the example described in the Core Foundations document section. + +* [repo_file example/echo-op/echo_op.cpp] + +[endsect] + + + +[section Common Code] + +This code is reused between some of the examples. The header files +stand alone can be directly included in your projects. + +* [repo_file example/common/detect_ssl.hpp] +* [repo_file example/common/helpers.hpp] +* [repo_file example/common/mime_types.hpp] +* [repo_file example/common/rfc7231.hpp] +* [repo_file example/common/ssl_stream.hpp] +* [repo_file example/common/write_msg.hpp] + +[endsect] + + + +[section Server Framework] + +This is a complete program and framework of classes implementing +a general purpose server that users may copy to use as the basis +for writing their own servers. It serves both HTTP and WebSocket. + +* [repo_file example/server-framework/file_service.hpp] +* [repo_file example/server-framework/framework.hpp] +* [repo_file example/server-framework/http_async_port.hpp] +* [repo_file example/server-framework/http_base.hpp] +* [repo_file example/server-framework/http_sync_port.hpp] +* [repo_file example/server-framework/https_ports.hpp] +* [repo_file example/server-framework/main.cpp] +* [repo_file example/server-framework/multi_port.hpp] +* [repo_file example/server-framework/server.hpp] +* [repo_file example/server-framework/service_list.hpp] +* [repo_file example/server-framework/ssl_certificate.hpp] +* [repo_file example/server-framework/ws_async_port.hpp] +* [repo_file example/server-framework/ws_sync_port.hpp] +* [repo_file example/server-framework/ws_upgrade_service.hpp] +* [repo_file example/server-framework/wss_ports.hpp] + +[endsect] + + + +[endsect] diff --git a/doc/3_0_core.qbk b/doc/3_0_core.qbk new file mode 100644 index 0000000000..634c730ed8 --- /dev/null +++ b/doc/3_0_core.qbk @@ -0,0 +1,33 @@ +[/ + 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) +] + +[section:using_io Using I/O] + +This library makes I/O primitives used by the implementation publicly +available so users can take advantage of them in their own libraries. +These primitives include traits, buffers, buffer algorithms, files, +and helpers for implementing asynchronous operations compatible with +__Asio__ and described in __N3747__. This section lists these facilities +by group, with descriptions. + +[important + This documentation assumes familiarity with __Asio__. Sample + code and identifiers used throughout are written as if the + following declarations are in effect: + + [snippet_core_1a] + [snippet_core_1b] +] + +[include 3_1_asio.qbk] +[include 3_2_streams.qbk] +[include 3_3_buffers.qbk] +[include 3_4_files.qbk] +[include 3_5_composed.qbk] +[include 3_6_detect_ssl.qbk] + +[endsect] diff --git a/doc/3_1_asio.qbk b/doc/3_1_asio.qbk new file mode 100644 index 0000000000..9070dac7c1 --- /dev/null +++ b/doc/3_1_asio.qbk @@ -0,0 +1,62 @@ +[/ + 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) +] + +[section Asio Refresher] + +[warning + Beast does not manage sockets, make outgoing connections, + accept incoming connections, handle timeouts, close endpoints, + do name lookups, deal with TLS certificates, perform authentication, + or otherwise handle any aspect of connection management. This is + left to the interfaces already existing on the underlying streams. +] + +Library stream algorithms require a __socket__, __ssl_stream__, or other +__Stream__ object that has already established communication with an +endpoint. This example is provided as a reminder of how to work with +sockets: + +[snippet_core_2] + +Throughout this documentation identifiers with the following names have +special meaning: + +[table Global Variables +[[Name][Description]] +[[ + [@http://www.boost.org/doc/html/boost_asio/reference/io_service.html [*`ios`]] +][ + A variable of type + [@http://www.boost.org/doc/html/boost_asio/reference/io_service.html `boost::asio::io_service`] + which is running on one separate thread, and upon which a + [@http://www.boost.org/doc/html/boost_asio/reference/io_service__work.html `boost::asio::io_service::work`] + object has been constructed. +]] +[[ + [@http://www.boost.org/doc/html/boost_asio/reference/ip__tcp/socket.html [*`sock`]] +][ + A variable of type + [@http://www.boost.org/doc/html/boost_asio/reference/ip__tcp/socket.html `boost::asio::ip::tcp::socket`] + which has already been connected to a remote host. +]] +[[ + [@http://www.boost.org/doc/html/boost_asio/reference/ssl__stream.html [*`ssl_sock`]] +][ + A variable of type + [@http://www.boost.org/doc/html/boost_asio/reference/ssl__stream.html `boost::asio::ssl::stream`] + which is already connected and has handshaked with a remote host. +]] +[[ + [link beast.ref.beast__websocket__stream [*`ws`]] +][ + A variable of type + [link beast.ref.beast__websocket__stream `websocket::stream`] + which is already connected with a remote host. +]] +] + +[endsect] diff --git a/doc/3_2_streams.qbk b/doc/3_2_streams.qbk new file mode 100644 index 0000000000..1778dff501 --- /dev/null +++ b/doc/3_2_streams.qbk @@ -0,0 +1,120 @@ +[/ + 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) +] + +[section Stream Types] + +A __Stream__ is a communication channel where data is transferred as +an ordered sequence of octet buffers. Streams are either synchronous +or asynchronous, and may allow reading, writing, or both. Note that +a particular type may model more than one concept. For example, the +Asio types __socket__ and __ssl_stream__ support both __SyncStream__ +and __AsyncStream__. All stream algorithms in Beast are declared as +template functions using these concepts: + +[table Stream Concepts +[[Concept][Description]] +[ + [__SyncReadStream__] + [ + Supports buffer-oriented blocking reads. + ] +][ + [__SyncWriteStream__] + [ + Supports buffer-oriented blocking writes. + ] +][ + [__SyncStream__] + [ + A stream supporting buffer-oriented blocking reads and writes. + ] +][ + [__AsyncReadStream__] + [ + Supports buffer-oriented asynchronous reads. + ] +][ + [__AsyncWriteStream__] + [ + Supports buffer-oriented asynchronous writes. + ] +][ + [__AsyncStream__] + [ + A stream supporting buffer-oriented asynchronous reads and writes. + ] +] +] + +These template metafunctions check whether a given type meets the +requirements for the various stream concepts, and some additional +useful utilities. The library uses these type checks internally +and also provides them as public interfaces so users may use the +same techniques to augment their own code. The use of these type +checks helps provide more concise errors during compilation: + +[table Stream Type Checks +[[Name][Description]] +[[ + [link beast.ref.beast__get_lowest_layer `get_lowest_layer`] +][ + Returns `T::lowest_layer_type` if it exists, else returns `T`. +]] +[[ + [link beast.ref.beast__has_get_io_service `has_get_io_service`] +][ + Determine if the `get_io_service` member function is present, + and returns an __io_service__. +]] +[[ + [link beast.ref.beast__is_async_read_stream `is_async_read_stream`] +][ + Determine if a type meets the requirements of __AsyncReadStream__. +]] +[[ + [link beast.ref.beast__is_async_stream `is_async_stream`] +][ + Determine if a type meets the requirements of both __AsyncReadStream__ + and __AsyncWriteStream__. +]] +[[ + [link beast.ref.beast__is_async_write_stream `is_async_write_stream`] +][ + Determine if a type meets the requirements of __AsyncWriteStream__. +]] +[[ + [link beast.ref.beast__is_completion_handler `is_completion_handler`] +][ + Determine if a type meets the requirements of __CompletionHandler__, + and is callable with a specified signature. +]] +[[ + [link beast.ref.beast__is_sync_read_stream `is_sync_read_stream`] +][ + Determine if a type meets the requirements of __SyncReadStream__. +]] +[[ + [link beast.ref.beast__is_sync_stream `is_sync_stream`] +][ + Determine if a type meets the requirements of both __SyncReadStream__ + and __SyncWriteStream__. +]] +[[ + [link beast.ref.beast__is_sync_write_stream `is_sync_write_stream`] +][ + Determine if a type meets the requirements of __SyncWriteStream__. +]] +] + +Using the type checks with `static_assert` on function or class template +types will provide users with helpful error messages and prevent undefined +behaviors. This example shows how a template function which writes to a +synchronous stream may check its argument: + +[snippet_core_3] + +[endsect] diff --git a/doc/3_3_buffers.qbk b/doc/3_3_buffers.qbk new file mode 100644 index 0000000000..21d9df3ed0 --- /dev/null +++ b/doc/3_3_buffers.qbk @@ -0,0 +1,161 @@ +[/ + 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) +] + +[section Buffer Types] + +__Asio__ provides the __ConstBufferSequence__ and __MutableBufferSequence__ +concepts, whose models provide ranges of buffers, as well as the __streambuf__ +class which encapsulates memory storage that may be automatically resized as +required, where the memory is divided into an input sequence followed by an +output sequence. The Networking TS (__N4588__) generalizes this `streambuf` +interface into the __DynamicBuffer__ concept. Beast algorithms which require +resizable buffers accept dynamic buffer objects as templated parameters. +These metafunctions check if types match the buffer concepts: + +[table Buffer Type Checks +[[Name][Description]] +[[ + [link beast.ref.beast__is_dynamic_buffer `is_dynamic_buffer`] +][ + Determine if a type meets the requirements of __DynamicBuffer__. +]] +[[ + [link beast.ref.beast__is_const_buffer_sequence `is_const_buffer_sequence`] +][ + Determine if a type meets the requirements of __ConstBufferSequence__. +]] +[[ + [link beast.ref.beast__is_mutable_buffer_sequence `is_mutable_buffer_sequence`] +][ + Determine if a type meets the requirements of __MutableBufferSequence__. +]] +] + +Beast provides several dynamic buffer implementations for a variety +of scenarios: + +[table Dynamic Buffer Implementations +[[Name][Description]] +[[ + [link beast.ref.beast__buffers_adapter `buffers_adapter`] +][ + This wrapper adapts any __MutableBufferSequence__ into a + __DynamicBuffer__ with an upper limit on the total size of the input and + output areas equal to the size of the underlying mutable buffer sequence. + The implementation does not perform heap allocations. +]] +[[ + [link beast.ref.beast__drain_buffer `drain_buffer`] +][ + A drain buffer has a small internal buffer and maximum size that + uses no dynamic allocation. It always has a size of zero, and + silently discards its input. This buffer may be passed to functions + which store data in a dynamic buffer when the caller wishes to + efficiently discard the data. +]] +[[ + [link beast.ref.beast__flat_buffer `flat_buffer`] + [link beast.ref.beast__basic_flat_buffer `basic_flat_buffer`] +][ + Guarantees that input and output areas are buffer sequences with + length one. Upon construction an optional upper limit to the total + size of the input and output areas may be set. The basic container + is an + [@http://en.cppreference.com/w/cpp/concept/AllocatorAwareContainer [*AllocatorAwareContainer]]. +]] +[[ + [link beast.ref.beast__multi_buffer `multi_buffer`] + [link beast.ref.beast__basic_multi_buffer `basic_multi_buffer`] +][ + Uses a sequence of one or more character arrays of varying sizes. + Additional character array objects are appended to the sequence to + accommodate changes in the size of the character sequence. The basic + container is an + [@http://en.cppreference.com/w/cpp/concept/AllocatorAwareContainer [*AllocatorAwareContainer]]. +]] +[[ + [link beast.ref.beast__static_buffer `static_buffer`] + [link beast.ref.beast__static_buffer `static_buffer_n`] +][ + Provides the facilities of a dynamic buffer, subject to an upper + limit placed on the total size of the input and output areas defined + by a constexpr template parameter. The storage for the sequences are + kept in the class; the implementation does not perform heap allocations. +]] +] + +Network applications frequently need to manipulate buffer sequences. To +facilitate working with buffers the library treats these sequences as +a special type of range. Algorithms and wrappers are provided which +transform these ranges efficiently using lazy evaluation. No memory +allocations are used in the transformations; instead, they create +lightweight iterators over the existing, unmodified memory buffers. +Control of buffers is retained by the caller; ownership is not +transferred. + +[table Buffer Algorithms and Types +[[Name][Description]] +[[ + [link beast.ref.beast__buffer_cat `buffer_cat`] +][ + This functions returns a new buffer sequence which, when iterated, + traverses the sequence which would be formed if all of the input buffer + sequences were concatenated. With this routine, multiple calls to a + stream's `write_some` function may be combined into one, eliminating + expensive system calls. +]] +[[ + [link beast.ref.beast__buffer_cat_view `buffer_cat_view`] +][ + This class represents the buffer sequence formed by concatenating + two or more buffer sequences. This is type of object returned by + [link beast.ref.beast__buffer_cat `buffer_cat`]. +]] +[[ + [link beast.ref.beast__buffer_prefix `buffer_prefix`] +][ + This function returns a new buffer or buffer sequence which represents + a prefix of the original buffers. +]] +[[ + [link beast.ref.beast__buffer_prefix_view `buffer_prefix_view`] +][ + This class represents the buffer sequence formed from a prefix of + an existing buffer sequence. This is the type of buffer returned by + [link beast.ref.beast__buffer_prefix.overload3 `buffer_prefix`]. +]] +[[ + [link beast.ref.beast__consuming_buffers `consuming_buffers`] +][ + This class wraps the underlying memory of an existing buffer sequence + and presents a suffix of the original sequence. The length of the suffix + may be progressively shortened. This lets callers work with sequential + increments of a buffer sequence. +]] +] + +These two functions facilitate buffer interoperability with standard +output streams. + +[table Buffer Output Streams +[[Name][Description]] +[[ + [link beast.ref.beast__buffers `buffers`] +][ + This function wraps a __ConstBufferSequence__ so it may be + used with `operator<<` and `std::ostream`. +]] +[[ + [link beast.ref.beast__ostream `ostream`] +][ + This function returns a `std::ostream` which wraps a dynamic buffer. + Characters sent to the stream using `operator<<` are stored in the + dynamic buffer. +]] +] + +[endsect] diff --git a/doc/3_4_files.qbk b/doc/3_4_files.qbk new file mode 100644 index 0000000000..020875f0b4 --- /dev/null +++ b/doc/3_4_files.qbk @@ -0,0 +1,38 @@ +[/ + 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) +] + +[section:files Files] + +Often when implementing network algorithms such as servers, it is necessary +to interact with files on the system. Beast defines the __File__ concept +and several models to facilitate cross-platform interaction with the +underlying filesystem: + +[table File Types +[[Name][Description]] +[[ + [link beast.ref.beast__file_stdio `file_stdio`] +][ + This implementation of __File__ uses the C++ standard library + facilities obtained by including ``. +]] +[[ + [link beast.ref.beast__file_win32 `file_win32`] +][ + This implements a __File__ for the Win32 API. It provides low level + access to the native file handle when necessary. +]] +[[ + [link beast.ref.beast__file_posix `file_posix`] +][ + For POSIX systems, this class provides a suitable implementation + of __File__ which wraps the native file descriptor and provides + it if necessary. +]] +] + +[endsect] diff --git a/doc/3_5_composed.qbk b/doc/3_5_composed.qbk new file mode 100644 index 0000000000..4d3eaa1ef1 --- /dev/null +++ b/doc/3_5_composed.qbk @@ -0,0 +1,236 @@ +[/ + 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) +] + +[section Writing Composed Operations] +[block''''''] + +Asynchronous operations are started by calling a free function or member +function known as an asynchronous ['__async_initfn__]. This function accepts +parameters specific to the operation as well as a "completion token." The +token is either a completion handler, or a type defining how the caller is +informed of the asynchronous operation result. __Asio__ comes with the +special tokens __use_future__ and __yield_context__ for using futures +and coroutines respectively. This system of customizing the return value +and method of completion notification is known as the +['Extensible Asynchronous Model] described in __N3747__, and a built in +to __N4588__. Here is an example of an initiating function which reads a +line from the stream and echoes it back. This function is developed +further in the next section: + +[example_core_echo_op_1] + +Authors using Beast can reuse the library's primitives to create their +own initiating functions for performing a series of other, intermediate +asynchronous operations before invoking a final completion handler. +The set of intermediate actions produced by an initiating function is +known as a +[@http://blog.think-async.com/2009/08/composed-operations-coroutines-and-code.html ['composed operation]]. +To ensure full interoperability and well-defined behavior, __Asio__ imposes +requirements on the implementation of composed operations. These classes +and functions make it easier to develop initiating functions and their +composed operations: + +[table Asynchronous Helpers +[[Name][Description]] +[[ + [link beast.ref.beast__async_completion `async_completion`] +][ + This class aggregates the completion handler customization point and + the asynchronous initiation function return value customization point + into a single object which exposes the appropriate output types for the + given input types, and also contains boilerplate that is necessary to + implement an initiation function using the Extensible Model. +]] +[[ + [link beast.ref.beast__async_return_type `async_return_type`] +][ + This template alias determines the return value of an asynchronous + initiation function given the completion token and signature. It is used + by asynchronous initiation functions to meet the requirements of the + Extensible Asynchronous Model. +]] +[[ + [link beast.ref.beast__bind_handler `bind_handler`] +][ + This function returns a new, nullary completion handler which when + invoked with no arguments invokes the original completion handler with a + list of bound arguments. The invocation is made from the same implicit + or explicit strand as that which would be used to invoke the original + handler. This is accomplished by using the correct overload of + `asio_handler_invoke` associated with the original completion handler. + +]] +[[ + [link beast.ref.beast__handler_alloc `handler_alloc`] +][ + This class meets the requirements of [*Allocator], and uses any custom + memory allocation and deallocation hooks associated with a given handler. + It is useful for when a composed operation requires temporary dynamic + allocations to achieve its result. Memory allocated using this allocator + must be freed before the final completion handler is invoked. +]] +[[ + [link beast.ref.beast__handler_ptr `handler_ptr`] +][ + This is a smart pointer container used to manage the internal state of a + composed operation. It is useful when the state is non trivial. For example + when the state has non-copyable or expensive to copy types. The container + takes ownership of the final completion handler, and provides boilerplate + to invoke the final handler in a way that also deletes the internal state. + The internal state is allocated using the final completion handler's + associated allocator, benefiting from all handler memory management + optimizations transparently. +]] +[[ + [link beast.ref.beast__handler_type `handler_type`] +][ + This template alias converts a completion token and signature to the + correct completion handler type. It is used in the implementation of + asynchronous initiation functions to meet the requirements of the + Extensible Asynchronous Model. +]] +] + + + +[section Echo] + +This example develops an initiating function called [*echo]. +The operation will read up to the first newline on a stream, and +then write the same line including the newline back on the stream. +The implementation performs both reading and writing, and has a +non-trivially-copyable state. +First we define the input parameters and results, then declare our +initiation function. For our echo operation the only inputs are the +stream and the completion token. The output is the error code which +is usually included in all completion handler signatures. + +[example_core_echo_op_2] + +Now that we have a declaration, we will define the body of the function. +We want to achieve the following goals: perform static type checking on +the input parameters, set up the return value as per __N3747__, and launch +the composed operation by constructing the object and invoking it. + +[example_core_echo_op_3] + +The initiating function contains a few relatively simple parts. There is +the customization of the return value type, static type checking, building +the return value type using the helper, and creating and launching the +composed operation object. The [*`echo_op`] object does most of the work +here, and has a somewhat non-trivial structure. This structure is necessary +to meet the stringent requirements of composed operations (described in more +detail in the __Asio__ documentation). We will touch on these requirements +without explaining them in depth. + +Here is the boilerplate present in all composed operations written +in this style: + +[example_core_echo_op_4] + +Next is to implement the function call operator. Our strategy is to make our +composed object meet the requirements of a completion handler by being copyable +(also movable), and by providing the function call operator with the correct +signature. Rather than using `std::bind` or `boost::bind`, which destroys +the type information and therefore breaks the allocation and invocation +hooks, we will simply pass `std::move(*this)` as the completion handler +parameter for any operations that we initiate. For the move to work correctly, +care must be taken to ensure that no access to data members are made after the +move takes place. Here is the implementation of the function call operator for +this echo operation: + +[example_core_echo_op_5] + +This is the most important element of writing a composed operation, and +the part which is often neglected or implemented incorrectly. It is the +declaration and definition of the "handler hooks". There are four hooks: + +[table Handler Hooks +[[Name][Description]] +[[ + [@http://www.boost.org/doc/html/boost_asio/reference/asio_handler_invoke.html `asio_handler_invoke`] +][ + Default invoke function for handlers. This hooking function ensures + that the invoked method used for the final handler is accessible at + each intermediate step. +]] +[[ + [@http://www.boost.org/doc/html/boost_asio/reference/asio_handler_allocate.html `asio_handler_allocate`] +][ + Default allocation function for handlers. Implement `asio_handler_allocate` + and `asio_handler_deallocate` for your own handlers to provide custom + allocation for temporary objects. +]] +[[ + [@http://www.boost.org/doc/html/boost_asio/reference/asio_handler_deallocate.html `asio_handler_deallocate`] +][ + Default deallocation function for handlers. Implement `asio_handler_allocate` + and `asio_handler_deallocate` for your own handlers to provide custom + allocation for temporary objects. +]] +[[ + [@http://www.boost.org/doc/html/boost_asio/reference/asio_handler_is_continuation.html `asio_handler_is_continuation`] +][ + Default continuation function for handlers. Implement + `asio_handler_is_continuation` for your own handlers to indicate when + a handler represents a continuation. +]] +] + +Our composed operation stores the final handler and performs its own +intermediate asynchronous operations. To ensure that I/O objects, in this +case the stream, are accessed safely it is important to use the same method +to invoke intermediate handlers as that used to invoke the final handler. +Similarly, for the memory allocation hooks our composed operation should use +the same hooks as those used by the final handler. And finally for the +`asio_is_continuation` hook, we want to return `true` for any intermediate +asynchronous operations we perform after the first one, since those represent +continuations. For the first asynchronous operation we perform, the hook should +return `true` only if the final handler also represents a continuation. Our +implementation of the hooks will forward the call to the corresponding +overloads of the final handler: + +[example_core_echo_op_6] + +There are some common mistakes that should be avoided when writing +composed operations: + +* Type erasing the final handler. This will cause undefined behavior. + +* Not using `std::addressof` to get the address of the handler. + +* Forgetting to include a return statement after calling an + initiating function. + +* Calling a synchronous function by accident. In general composed + operations should not block for long periods of time, since this + ties up a thread running on the __io_service__. + +* Forgetting to overload `asio_handler_invoke` for the composed + operation. This will cause undefined behavior if someone calls + the initiating function with a strand-wrapped function object, + and there is more than thread running on the `io_service`. + +* For operations which complete immediately (i.e. without calling an + intermediate initiating function), forgetting to use `io_service::post` + to invoke the final handler. This breaks the following initiating + function guarantee: ['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`]. + The function + [link beast.ref.beast__bind_handler `bind_handler`] + is provided for this purpose. + +A complete, runnable version of this example may be found in the examples +directory. + +[endsect] + + + +[endsect] diff --git a/doc/3_6_detect_ssl.qbk b/doc/3_6_detect_ssl.qbk new file mode 100644 index 0000000000..da6350b823 --- /dev/null +++ b/doc/3_6_detect_ssl.qbk @@ -0,0 +1,67 @@ +[/ + 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) +] + +[section Example: Detect SSL] + +In this example we will build a simple function to detect the presence +of the SSL handshake given an input buffer sequence. Then we build on +the example by adding a synchronous stream algorithm. Finally, we +implemement an asynchronous detection function using a composed operation. +This SSL detector may be used to allow a server to accept both SSL/TLS and +unencrypted connections at the same port. + +Here is the declaration for a function to detect the SSL client handshake. +The input to the function is simply a buffer sequence, no stream. This +allows the detection algorithm to be used elsewhere. + +[example_core_detect_ssl_1] + +The implementation checks the buffer for the presence of the SSL +Handshake message octet sequence and returns an apporopriate value: + +[example_core_detect_ssl_2] + +Now we define a stream operation. We start with the simple, +synchronous version which takes the stream and buffer as input: + +[example_core_detect_ssl_3] + +The synchronous algorithm is the model for building the asynchronous +operation which has more boilerplate. First, we declare the asynchronous +initiating function: + +[example_core_detect_ssl_4] + +The implementation of the initiating function is straightforward +and contains mostly boilerplate. It is to construct the return +type customization helper to obtain the actual handler, and +then create the composed operation and launch it. The actual +code for interacting with the stream is in the composed operation, +which is written as a separate class. + +[example_core_detect_ssl_5] + +Now we will declare our composed operation. There is a considerable +amount of necessary boilerplate to get this right, but the result +is worth the effort. + +[example_core_detect_ssl_6] + +The boilerplate is all done, and now we need to implement the function +call operator that turns this composed operation a completion handler +with the signature `void(error_code, std::size_t)` which is exactly +the signature needed when performing asynchronous reads. This function +is a transformation of the synchronous version of `detect_ssl` above, +but with the inversion of flow that characterizes code written in the +callback style: + +[example_core_detect_ssl_7] + +This SSL detector is used by the server framework in the example +directory. + +[endsect] diff --git a/doc/5_00_http.qbk b/doc/5_00_http.qbk new file mode 100644 index 0000000000..6a90c066a3 --- /dev/null +++ b/doc/5_00_http.qbk @@ -0,0 +1,91 @@ +[/ + 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) +] + +[section Using HTTP] + +[warning + Higher level functions such as Basic + Authentication, mime/multipart encoding, cookies, automatic handling + of redirects, gzipped transfer encodings, caching, or proxying (to name + a few) are not directly provided, but nothing stops users from creating + these features using Beast's HTTP message types. +] + +This library offers programmers simple and performant models of HTTP messages +and their associated operations including synchronous, asynchronous, and +buffer-oriented parsing and serialization of messages in the HTTP/1 wire +format using __Asio__. Specifically, the library provides: + +[variablelist +[ + [Message Containers] + [ + Complete HTTP messages are modeled using the __message__ class, + with possible user customizations. + ] +][ + [Stream Reading] + [ + The functions + [link beast.ref.beast__http__read `read`], + [link beast.ref.beast__http__read_header `read_header`], + [link beast.ref.beast__http__read_some `read_some`], + [link beast.ref.beast__http__async_read `async_read`], + [link beast.ref.beast__http__async_read_header `async_read_header`], and + [link beast.ref.beast__http__async_read_some `async_read_some`] + read HTTP/1 message data from a + [link beast.concept.streams stream]. +] +][ + [Stream Writing] + [ + The functions + [link beast.ref.beast__http__write `write`], + [link beast.ref.beast__http__write_header `write_header`], + [link beast.ref.beast__http__write_some `write_some`], + [link beast.ref.beast__http__async_write `async_write`], + [link beast.ref.beast__http__async_write_header `async_write_header`], and + [link beast.ref.beast__http__async_write_some `async_write_some`] + write HTTP/1 message data to a + [link beast.concept.streams stream]. + ] +][ + [Serialization] + [ + The __serializer__ produces a series of octet buffers + conforming to the __rfc7230__ wire representation of + a __message__. + ] +][ + [Parsing] + [ + The __parser__ attempts to convert a series of octet + buffers into a __message__. + ] +] +] + +[note + This documentation assumes some familiarity with __Asio__ and + the HTTP protocol specification described in __rfc7230__. Sample + code and identifiers mentioned in this section is written as if + these declarations are in effect: + + [http_snippet_1] +] + +[include 5_01_primer.qbk] +[include 5_02_message.qbk] +[include 5_03_streams.qbk] +[include 5_04_serializer_streams.qbk] +[include 5_05_parser_streams.qbk] +[include 5_06_serializer_buffers.qbk] +[include 5_07_parser_buffers.qbk] +[include 5_08_custom_body.qbk] +[include 5_09_custom_parsers.qbk] + +[endsect] diff --git a/doc/5_01_primer.qbk b/doc/5_01_primer.qbk new file mode 100644 index 0000000000..78bd5d051e --- /dev/null +++ b/doc/5_01_primer.qbk @@ -0,0 +1,153 @@ +[/ + 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) +] + +[section Protocol Primer] + +The HTTP protocol defines the +[@https://tools.ietf.org/html/rfc7230#section-2.1 client and server roles]: +clients send requests and servers send back responses. When a client and +server have established a connection, the client sends a series of requests +while the server sends back at least one response for each received request +in the order those requests were received. + +A request or response is an +[@https://tools.ietf.org/html/rfc7230#section-3 HTTP message] +(referred to hereafter as "message") having two parts: +a header with structured metadata and an optional variable-length body +holding arbitrary data. A serialized header is one or more text lines +where each line ends in a carriage return followed by linefeed (`"\r\n"`). +An empty line marks the end of the header. The first line in the header +is called the ['start-line]. The contents of the start line contents are +different for requests and responses. + +Every message contains a set of zero or more field name/value pairs, +collectively called "fields". The names and values are represented using +text strings with various requirements. A serialized field contains the +field name, then a colon followed by a space (`": "`), and finally the field +value with a trailing CRLF. + +[heading Requests] + +Clients send requests, which contain a +[@https://tools.ietf.org/html/rfc7230#section-3.1.1 method] +and +[@https://tools.ietf.org/html/rfc7230#section-5.3 request-target], +and +[@https://tools.ietf.org/html/rfc7230#section-2.6 HTTP-version]. +The method identifies the operation to be performed while the target +identifies the object on the server to which the operation applies. +The version is almost always 1.1, but older programs sometimes use 1.0. + +[table +[[Serialized Request][Description]] +[[ +``` + GET / HTTP/1.1\r\n + User-Agent: Beast\r\n + \r\n +``` +][ + This request has a method of "GET", a target of "/", and indicates + HTTP version 1.1. It contains a single field called "User-Agent" + whose value is "Beast". There is no message body. +]] +] + +[heading Responses] + +Servers send responses, which contain a +[@https://tools.ietf.org/html/rfc7231#section-6 status-code], +[@https://tools.ietf.org/html/rfc7230#section-3.1.2 reason-phrase], and +[@https://tools.ietf.org/html/rfc7230#section-2.6 HTTP-version]. +The reason phrase is +[@https://tools.ietf.org/html/rfc7230#section-3.1.2 obsolete]: +clients SHOULD ignore the reason-phrase content. Here is a response which +includes a body. The special +[@https://tools.ietf.org/html/rfc7230#section-3.3.2 Content-Length] +field informs the remote host of the size of the body which follows. + +[table +[[Serialized Response][Description]] +[[ +``` + HTTP/1.1 200 OK\r\n + Server: Beast\r\n + Content-Length: 13\r\n + \r\n + Hello, world! +``` +][ + This response has a + [@https://tools.ietf.org/html/rfc7231#section-6 200 status code] + meaning the operation requested completed successfully. The obsolete + reason phrase is "OK". It specifies HTTP version 1.1, and contains + a body 13 octets in size with the text "Hello, world!". +]] +] + +[heading Body] + +Messages may optionally carry a body. The size of the message body +is determined by the semantics of the message and the special fields +Content-Length and Transfer-Encoding. +[@https://tools.ietf.org/html/rfc7230#section-3.3 rfc7230 section 3.3] +provides a comprehensive description for how the body length is +determined. + +[heading Special Fields] + +Certain fields appearing in messages are special. The library understands +these fields when performing serialization and parsing, taking automatic +action as needed when the fields are parsed in a message and also setting +the fields if the caller requests it. + +[table Special Fields +[[Field][Description]] +[ + [ + [@https://tools.ietf.org/html/rfc7230#section-6.1 [*`Connection`]] + + [@https://tools.ietf.org/html/rfc7230#appendix-A.1.2 [*`Proxy-Connection`]] + ][ + This field allows the sender to indicate desired control options + for the current connection. Common values include "close", + "keep-alive", and "upgrade". + ] +][ + [ + [@https://tools.ietf.org/html/rfc7230#section-3.3.2 [*`Content-Length`]] + ][ + When present, this field informs the recipient about the exact + size in bytes of the body which follows the message header. + ] +][ + [ + [@https://tools.ietf.org/html/rfc7230#section-3.3.1 [*`Transfer-Encoding`]] + ][ + This optional field lists the names of the sequence of transfer codings + that have been (or will be) applied to the content payload to form + the message body. + + Beast understands the "chunked" coding scheme when it is the last + (outermost) applied coding. The library will automatically apply + chunked encoding when the content length is not known ahead of time + during serialization, and the library will automatically remove chunked + encoding from parsed messages when present. + ] +][ + [ + [@https://tools.ietf.org/html/rfc7230#section-6.7 [*`Upgrade`]] + ][ + The Upgrade header field provides a mechanism to transition from + HTTP/1.1 to another protocol on the same connection. For example, it + is the mechanism used by WebSocket's initial HTTP handshake to + establish a WebSocket connection. + ] +] +] + +[endsect] diff --git a/doc/5_02_message.qbk b/doc/5_02_message.qbk new file mode 100644 index 0000000000..f9f4a3decb --- /dev/null +++ b/doc/5_02_message.qbk @@ -0,0 +1,231 @@ +[/ + 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) +] + +[section Message Containers] + +Beast provides a single class template __message__ and some aliases which +model HTTP/1 and +[@https://tools.ietf.org/html/rfc7540 HTTP/2] +messages: + +[table Message +[[Name][Description]] +[[ + __message__ +][ + ``` + /// An HTTP message + template< + bool isRequest, // `true` for requests, `false` for responses + class Body, // Controls the container and algorithms used for the body + class Fields = fields> // The type of container to store the fields + class message; + ``` +]] +[[ + [link beast.ref.beast__http__request `request`] +][ + ``` + /// A typical HTTP request + template + using request = message; + ``` +]] +[[ + [link beast.ref.beast__http__response `response`] +][ + ``` + /// A typical HTTP response + template + using response = message; + ``` +]] +] + +The container offers value semantics including move and copy if supported +by __Body__ and __Fields__. User defined template function parameters can +accept any message, or can use partial specialization to accept just +requests or responses. The default __fields__ is a provided associative +container using the standard allocator and supporting modification and +inspection of fields. As per __rfc7230__, a non-case-sensitive comparison +is used for field names. User defined types for fields are possible. +The `Body` type determines the type of the container used to represent the +body as well as the algorithms for transferring buffers to and from the +the container. The library comes with a collection of common body types. +As with fields, user defined body types are possible. + +Sometimes it is desired to only work with a header. Beast provides a single +class template __header__ and some aliases to model HTTP/1 and HTTP/2 headers: + +[table Header +[[Name][Description]] +[[ + __header__ +][ + ``` + /// An HTTP header + template< + bool isRequest, // `true` for requests, `false` for responses + class Fields = fields> // The type of container to store the fields + class header; + ``` +]] +[[ + [link beast.ref.beast__http__request_header `request_header`] +][ + ``` + /// A typical HTTP request header + template + using request_header = header; + ``` +]] +[[ + [link beast.ref.beast__http__response_header `response_header`] +][ + ``` + /// A typical HTTP response header + template + using response_header = header; + ``` +]] +] + +Requests and responses share the version, fields, and body but have +a few members unique to the type. This is implemented by declaring the +header classes as partial specializations of `isRequest`. __message__ +is derived from __header__; a message may be passed as an argument to +a function taking a suitably typed header as a parameter. Additionally, +`header` is publicly derived from `Fields`; a message inherits all the +member functions of `Fields`. This diagram shows the inheritance +relationship between header and message, along with some of the +notable differences in members in each partial specialization: + +[$images/message.png [width 730px] [height 410px]] + +[heading:body Body Types] + +Beast defines the __Body__ concept, which determines both the type of +the [link beast.ref.beast__http__message.body `message::body`] member +(as seen in the diagram above) and may also include algorithms for +transferring buffers in and out. These algorithms are used during +parsing and serialization. Users may define their own body types which +meet the requirements, or use the ones that come with the library: + +[table +[[Name][Description]] +[[ + [link beast.ref.beast__http__buffer_body `buffer_body`] +][ + A body whose + [link beast.ref.beast__http__buffer_body__value_type `value_type`] + holds a raw pointer and size to a caller-provided buffer. + This allows for serialization of body data coming from + external sources, and incremental parsing of message body + content using a fixed size buffer. +]] +[[ + [link beast.ref.beast__http__dynamic_body `dynamic_body`] + + [link beast.ref.beast__http__basic_dynamic_body `basic_dynamic_body`] +][ + A body whose `value_type` is a __DynamicBuffer__. It inherits + the insertion complexity of the underlying choice of dynamic buffer. + Messages with this body type may be serialized and parsed. +]] +[[ + [link beast.ref.beast__http__empty_body `empty_body`] +][ + A special body with an empty `value_type` indicating that the + message has no body. Messages with this body may be serialized + and parsed; however, body octets received while parsing a message + with this body will generate a unique error. +]] +[[ + [link beast.ref.beast__http__file_body `file_body`] +][ + This body is represented by a file opened for either reading or + writing. Messages with this body may be serialized and parsed. + HTTP algorithms will use the open file for reading and writing, + for streaming and incremental sends and receives. +]] +[[ + [link beast.ref.beast__http__span_body `span_body`] +][ + A body whose `value_type` is a + [link beast.ref.beast__span `span`], + a non-owning reference to a single linear buffer of bytes. + Messages with this body type may be serialized and parsed. +]] +[[ + [link beast.ref.beast__http__basic_string_body `basic_string_body`] + + [link beast.ref.beast__http__string_body `string_body`] +][ + A body whose `value_type` is `std::basic_string` or `std::string`. + Insertion complexity is amortized constant time, while capacity + grows geometrically. Messages with this body type may be serialized + and parsed. This is the type of body used in the examples. +]] +[[ + [link beast.ref.beast__http__vector_body `vector_body`] +][ + A body whose `value_type` is `std::vector`. Insertion complexity + is amortized constant time, while capacity grows geometrically. + Messages with this body type may be serialized and parsed. +]] +] + +[heading Usage] + +These examples show how to create and fill in request and response +objects: Here we build an +[@https://tools.ietf.org/html/rfc7231#section-4.3.1 HTTP GET] +request with an empty message body: + +[table Create Request +[[Statements] [Serialized Result]] +[[ + [http_snippet_2] +][ +``` + GET /index.htm HTTP/1.1\r\n + Accept: text/html\r\n + User-Agent: Beast\r\n + \r\n +``` +]] +] + +In this code we create an HTTP response with a status code indicating success. +This message has a body with a non-zero length. The function +[link beast.ref.beast__http__message.prepare_payload `message::prepare_payload`] +automatically sets the Content-Length or Transfer-Encoding field +depending on the content and type of the `body` member. Use of this function +is optional; these fields may also be set explicitly. + +[table Create Response +[[Statements] [Serialized Result]] +[[ + [http_snippet_3] +][ +``` + HTTP/1.1 200 OK\r\n + Server: Beast\r\n + Content-Length: 13\r\n + \r\n + Hello, world! +``` +]] +] + +The implementation will automatically fill in the obsolete +[@https://tools.ietf.org/html/rfc7230#section-3.1.2 reason-phrase] +from the status code when serializing a message. Or it may +be set directly using +[link beast.ref.beast__http__header.reason.overload2 `header::reason`]. + +[endsect] diff --git a/doc/5_03_streams.qbk b/doc/5_03_streams.qbk new file mode 100644 index 0000000000..31cd8abc65 --- /dev/null +++ b/doc/5_03_streams.qbk @@ -0,0 +1,107 @@ +[/ + 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) +] + +[section Message Stream Operations] + +Beast provides synchronous and asynchronous algorithms to parse and +serialize HTTP/1 wire format messages on streams. These functions form +the message-oriented stream interface: + +[table Message Stream Operations +[[Name][Description]] +[[ + [link beast.ref.beast__http__read.overload3 [*read]] +][ + Read a __message__ from a __SyncReadStream__. +]] +[[ + [link beast.ref.beast__http__async_read.overload2 [*async_read]] +][ + Read a __message__ from an __AsyncReadStream__. +]] +[[ + [link beast.ref.beast__http__write.overload1 [*write]] +][ + Write a __message__ to a __SyncWriteStream__. +]] +[[ + [link beast.ref.beast__http__async_write [*async_write]] +][ + Write a __message__ to an __AsyncWriteStream__. +]] +] + +All synchronous stream operations come in two varieties. One which throws +an exception upon error, and another which accepts as the last parameter an +argument of type [link beast.ref.beast__error_code `error_code&`]. If an error +occurs this argument will be set to contain the error code. + + + +[heading Reading] + +Because a serialized header is not length-prefixed, algorithms which +parse messages from a stream may read past the end of a message for +efficiency. To hold this surplus data, all stream read operations use +a passed-in __DynamicBuffer__ which must be persisted between calls. +Each read operation may consume bytes remaining in the buffer, and +leave behind new bytes. In this example we declare the buffer and a +message variable, then read a complete HTTP request synchronously: + +[http_snippet_4] + +This example uses __flat_buffer__. Beast's __basic_parser__ is +optimized for structured HTTP data located in a single contiguous +(['flat]) memory buffer. When not using a flat buffer the implementation +may perform an additional memory allocations to restructure the input +into a single buffer for parsing. + +[tip + Other Implementations of __DynamicBuffer__ may avoid parser + memory allocation by always returning buffer sequences of + length one. +] + +Messages may also be read asynchronously. When performing asynchronous +stream read operations the stream, buffer, and message variables must +remain valid until the operation has completed. Beast asynchronous +initiation functions use Asio's completion handler model. This call +reads a message asynchronously and report the error code upon +completion: + +[http_snippet_5] + +If a read stream algorithm cannot complete its operation without exceeding +the maximum specified size of the dynamic buffer provided, the error +[link beast.ref.beast__http__error `buffer_overflow`] +is returned. This may be used to impose a limit on the maximum size of an +HTTP message header for protection from buffer overflow attacks. The +following code will print the error message: + +[http_snippet_6] + + + +[heading Writing] + +A set of free functions allow serialization of an entire HTTP message to +a stream. If a response has no declared content length and no chunked +transfer encoding, then the end of the message is indicated by the server +closing the connection. When sending such a response, Beast will return the +[link beast.ref.beast__http__error `error::end_of_stream`] +from the write algorithm to indicate +to the caller that the connection should be closed. This example +constructs and sends a response whose body length is determined by +the number of octets received prior to the server closing the connection: + +[http_snippet_7] + +The asynchronous version could be used instead: + +[http_snippet_8] + +[endsect] diff --git a/doc/5_04_serializer_streams.qbk b/doc/5_04_serializer_streams.qbk new file mode 100644 index 0000000000..62a556ac29 --- /dev/null +++ b/doc/5_04_serializer_streams.qbk @@ -0,0 +1,150 @@ +[/ + 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) +] + +[section Serializer Stream Operations] + +Non-trivial algorithms need to do more than send entire messages +at once, such as: + +* Send the header first, and the body later. + +* Set chunk extensions or trailers using a chunk decorator. + +* Send a message incrementally: bounded work in each I/O cycle. + +* Use a series of caller-provided buffers to represent the body. + +These tasks may be performed by using the serializer stream interfaces. +To use these interfaces, first construct a suitable object with +the message to be sent: + +[table Serializer +[[Name][Description]] +[[ + __serializer__ +][ + ``` + /// Provides buffer oriented HTTP message serialization functionality. + template< + bool isRequest, + class Body, + class Fields = fields, + class ChunkDecorator = no_chunk_decorator + > + class serializer; + ``` +]] +[[ + [link beast.ref.beast__http__request_serializer `request_serializer`] +][ + ``` + /// A serializer for HTTP/1 requests + template< + class Body, + class Fields = fields, + class ChunkDecorator = no_chunk_decorator> + using request_serializer = serializer; + ``` +]] +[[ + [link beast.ref.beast__http__response_serializer `response_serializer`] +][ + ``` + /// A serializer for HTTP/1 responses + template< + class Body, + class Fields = fields, + class ChunkDecorator = no_chunk_decorator> + using response_serializer = serializer; + ``` +]] +] + +The choices for template types must match the message passed on construction. +This code creates an HTTP response and the corresponding serializer: + +[http_snippet_10] + +The stream operations which work on serializers are: + +[table Serializer Stream Operations +[[Name][Description]] +[[ + [link beast.ref.beast__http__write.overload1 [*write]] +][ + Send everything in a __serializer__ to a __SyncWriteStream__. +]] +[[ + [link beast.ref.beast__http__async_write.overload1 [*async_write]] +][ + Send everything in a __serializer__ asynchronously to an __AsyncWriteStream__. +]] +[[ + [link beast.ref.beast__http__write_header.overload1 [*write_header]] +][ + Send only the header from a __serializer__ to a __SyncWriteStream__. +]] +[[ + [link beast.ref.beast__http__async_write_header [*async_write_header]] +][ + Send only the header from a __serializer__ asynchronously to an __AsyncWriteStream__. +]] +[[ + [link beast.ref.beast__http__write_some.overload1 [*write_some]] +][ + Send part of a __serializer__ to a __SyncWriteStream__. +]] +[[ + [link beast.ref.beast__http__async_write_some [*async_write_some]] +][ + Send part of a __serializer__ asynchronously to an __AsyncWriteStream__. +]] +] + +Here is an example of using a serializer to send a message on a stream +synchronously. This performs the same operation as calling `write(stream, m)`: + +[http_snippet_12] + +[heading Chunk Decorators] + +When the message used to construct the serializer indicates the chunked +transfer encoding, the serializer will automatically generate the proper +encoding in the output buffers. __rfc7230__ defines additional fields +called the +[@https://tools.ietf.org/html/rfc7230#section-4.1.1 chunk extensions] +in chunks with body octets, and the +[@https://tools.ietf.org/html/rfc7230#section-4.1.2 chunked trailer part] +for the final chunk. Applications that wish to emit chunk extensions +and trailers may instantiate the serializer with a "chunk decorator" type, +and pass an instance of the type upon construction. This decorator is +a function object which, when invoked with a __ConstBufferSequence__, +returns a +[link beast.ref.beast__string_view `string_view`] containing either the extensions +or the trailer. For chunks containing body data, the passed buffer will +contain one or more corresponding body octets. The decorator may use this +information as needed. For example, to compute a digest on the data and +store it as a chunk extension. For the trailers, the serializer will +invoke the decorator with a buffer sequence of size zero. Or more +specifically, with an object of type +[@http://www.boost.org/doc/html/boost_asio/reference/null_buffers.html `boost::asio::null_buffers`]. + +For body chunks the string returned by the decorator must follow the +[@https://tools.ietf.org/html/rfc7230#section-4.1.1 correct syntax] +for the entire chunk extension. For the trailer, the returned string +should consist of zero or more lines ending in a CRLF and containing +a field name/value pair in the format prescribed by __rfc7230__. It +is the responsibility of the decorator to manage returned string buffers. +The implementation guarantees it will not reference previous strings +after subsequent calls. + +This defines a decorator which sets an extension variable `x` equal +to the size of the chunk in bytes, and returns a single trailer field: + +[http_snippet_17] + +[endsect] diff --git a/doc/5_05_parser_streams.qbk b/doc/5_05_parser_streams.qbk new file mode 100644 index 0000000000..465e1a9fb7 --- /dev/null +++ b/doc/5_05_parser_streams.qbk @@ -0,0 +1,138 @@ +[/ + 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) +] + +[section Parser Stream Operations] + +Non-trivial algorithms need to do more than receive entire messages +at once, such as: + + +* Receive the header first and body later. + +* Receive a large body using a fixed-size buffer. + +* Receive a message incrementally: bounded work in each I/O cycle. + +* Defer the commitment to a __Body__ type until after reading the header. + +These types of operations require callers to manage the lifetime of +associated state, by constructing a class derived from __basic_parser__. +Beast comes with the derived instance __parser__ which creates complete +__message__ objects using the __basic_fields__ Fields container. + +[table Parser +[[Name][Description]] +[[ + __parser__ +][ + ``` + /// An HTTP/1 parser for producing a message. + template< + bool isRequest, // `true` to parse an HTTP request + class Body, // The Body type for the resulting message + class Allocator = std::allocator> // The type of allocator for the header + class parser + : public basic_parser<...>; + ``` +]] +[[ + [link beast.ref.beast__http__request_parser `request_parser`] +][ + ``` + /// An HTTP/1 parser for producing a request message. + template> + using request_parser = parser; + ``` +]] +[[ + [link beast.ref.beast__http__response_parser `response_parser`] +][ + ``` + /// An HTTP/1 parser for producing a response message. + template> + using response_parser = parser; + ``` +]] +] + +[note + The __basic_parser__ and classes derived from it handle octet streams + serialized in the HTTP/1 format described in __rfc7230__. +] + +The stream operations which work on parsers are: + +[table Parser Stream Operations +[[Name][Description]] +[[ + [link beast.ref.beast__http__read.overload1 [*read]] +][ + Read everything into a parser from a __SyncWriteStream__. +]] +[[ + [link beast.ref.beast__http__async_read.overload1 [*async_read]] +][ + Read everything into a parser asynchronously from an __AsyncWriteStream__. +]] +[[ + [link beast.ref.beast__http__read_header.overload1 [*read_header]] +][ + Read only the header octets into a parser from a __SyncWriteStream__. +]] +[[ + [link beast.ref.beast__http__async_read_header [*async_read_header]] +][ + Read only the header octets into a parser asynchronously from an __AsyncWriteStream__. +]] +[[ + [link beast.ref.beast__http__read_some.overload1 [*read_some]] +][ + Read some octets into a parser from a __SyncReadStream__. +]] +[[ + [link beast.ref.beast__http__async_read_some [*async_read_some]] +][ + Read some octets into a parser asynchronously from an __AsyncWriteStream__. +]] +] + +As with message stream operations, parser stream operations require a +persisted __DynamicBuffer__ for holding unused octets from the stream. +The basic parser implementation is optimized for the case where this dynamic +buffer stores its input sequence in a single contiguous memory buffer. It is +advised to use an instance of __flat_buffer__, __static_buffer__, or +__static_buffer_n__ for this purpose, although a user defined instance of +__DynamicBuffer__ which produces input sequences of length one is also suitable. + +The parser contains a message constructed internally. Arguments passed +to the parser's constructor are forwarded into the message container. +The caller can access the message inside the parser by calling +[link beast.ref.beast__http__parser.get `parser::get`]. +If the `Fields` and `Body` types are [*MoveConstructible], the caller +can take ownership of the message by calling +[link beast.ref.beast__http__parser.release `parser::release`]. In this example +we read an HTTP response with a string body using a parser, then print +the response: + +[http_snippet_13] + + + +[section Incremental Read] + +This function uses +[link beast.ref.beast__http__buffer_body `buffer_body`] +and parser stream operations to read a message body progressively +using a small, fixed-size buffer: + +[example_incremental_read] + +[endsect] + + + +[endsect] diff --git a/doc/5_06_serializer_buffers.qbk b/doc/5_06_serializer_buffers.qbk new file mode 100644 index 0000000000..8e2446e3b9 --- /dev/null +++ b/doc/5_06_serializer_buffers.qbk @@ -0,0 +1,79 @@ +[/ + 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) +] + +[section Buffer-Oriented Serializing] +[block''''''] + +An instance of __serializer__ can be invoked directly, without using +the provided stream operations. This could be useful for implementing +algorithms on objects whose interface does not conform to __Stream__. +For example, a +[@https://github.com/libuv/libuv *libuv* socket]. +The serializer interface is interactive; the caller invokes it repeatedly +to produce buffers until all of the buffers have been generated. Then the +serializer is destroyed. + +To obtain the serialized next buffer sequence, call +[link beast.ref.beast__http__serializer.next `serializer::next`]. +Then, call +[link beast.ref.beast__http__serializer.consume `serializer::consume`] +to indicate the number of bytes consumed. This updates the next +set of buffers to be returned, if any. +`serializer::next` takes an error code parameter and invokes a visitor +argument with the error code and buffer of unspecified type. In C++14 +this is easily expressed with a generic lambda. The function +[link beast.ref.beast__http__serializer.is_done `serializer::is_done`] +will return `true` when all the buffers have been produced. This C++14 +example prints the buffers to standard output: + +[http_snippet_14] + +Generic lambda expressions are only available in C++14 or later. A functor +with a templated function call operator is necessary to use C++11 as shown: + +[http_snippet_15] + +[heading Split Serialization] + +In some cases, such as the handling of the +[@https://tools.ietf.org/html/rfc7231#section-5.1.1 Expect: 100-continue] +field, it may be desired to first serialize the header, perform some other +action, and then continue with serialization of the body. This is +accomplished by calling +[link beast.ref.beast__http__serializer.split `serializer::split`] +with a boolean indicating that when buffers are produced, the last buffer +containing serialized header octets will not contain any octets corresponding +to the body. The function +[link beast.ref.beast__http__serializer.is_header_done `serializer::is_header_done`] +informs the caller whether the header been serialized fully. In this +C++14 example we print the header first, followed by the body: + +[http_snippet_16] + + + +[section Write To std::ostream] + +The standard library provides the type `std::ostream` for performing high +level write operations on character streams. The variable `std::cout` is +based on this output stream. This example uses the buffer oriented interface +of __serializer__ to write an HTTP message to a `std::ostream`: + +[example_http_write_ostream] + +[tip + Serializing to a `std::ostream` could be implemented using an alternate + strategy: adapt the `std::ostream` interface to a __SyncWriteStream__, + enabling use with the library's existing stream algorithms. This is + left as an exercise for the reader. +] + +[endsect] + + + +[endsect] diff --git a/doc/5_07_parser_buffers.qbk b/doc/5_07_parser_buffers.qbk new file mode 100644 index 0000000000..11be8576c4 --- /dev/null +++ b/doc/5_07_parser_buffers.qbk @@ -0,0 +1,107 @@ +[/ + 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) +] + +[section Buffer-Oriented Parsing] +[block''''''] + +A subclass of __basic_parser__ can be invoked directly, without using +the provided stream operations. This could be useful for implementing +algorithms on objects whose interface does not conform to __Stream__. +For example, a +[@http://zeromq.org/ *ZeroMQ* socket]. +The basic parser interface is interactive; the caller invokes the function +[link beast.ref.beast__http__basic_parser.put `basic_parser::put`] +repeatedly with buffers until an error occurs or the parsing is done. The +function +[link beast.ref.beast__http__basic_parser.put_eof `basic_parser::put_eof`] +Is used when the caller knows that there will never be more data (for example, +if the underlying connection is closed), + +[heading Parser Options] + +The parser provides a few options which may be set before parsing begins: + +[table Parser Options +[[Name][Default][Description]] +[[ + [link beast.ref.beast__http__basic_parser.eager.overload2 `eager`] +][ + `false` +][ + Normally the parser returns after successfully parsing a structured + element (header, chunk header, or chunk body) even if there are octets + remaining in the input. This is necessary when attempting to parse the + header first, or when the caller wants to inspect information which may + be invalidated by subsequent parsing, such as a chunk extension. The + `eager` option controls whether the parser keeps going after parsing + structured element if there are octets remaining in the buffer and no + error occurs. This option is automatically set or cleared during certain + stream operations to improve performance with no change in functionality. +]] +[[ + [link beast.ref.beast__http__basic_parser.skip.overload2 `skip`] +][ + `false` +][ + This option controls whether or not the parser expects to see an HTTP + body, regardless of the presence or absence of certain fields such as + Content-Length or a chunked Transfer-Encoding. Depending on the request, + some responses do not carry a body. For example, a 200 response to a + [@https://tools.ietf.org/html/rfc7231#section-4.3.6 CONNECT] request + from a tunneling proxy, or a response to a + [@https://tools.ietf.org/html/rfc7231#section-4.3.2 HEAD] request. + In these cases, callers may use this function inform the parser that + no body is expected. The parser will consider the message complete + after the header has been received. +]] +[[ + [link beast.ref.beast__http__basic_parser.body_limit `body_limit`] +][ + 1MB/8MB +][ + This function sets the maximum allowed size of the content body. + When a body larger than the specified size is detected, an error + is generated and parsing terminates. This setting helps protect + servers from resource exhaustion attacks. The default limit when + parsing requests is 1MB, and for parsing responses 8MB. +]] +[[ + [link beast.ref.beast__http__basic_parser.header_limit `header_limit`] +][ + 8KB +][ + This function sets the maximum allowed size of the header + including all field name, value, and delimiter characters + and also including the CRLF sequences in the serialized + input. +]] +] + + + +[section Read From std::istream] + +The standard library provides the type `std::istream` for performing high +level read operations on character streams. The variable `std::cin` is based +on this input stream. This example uses the buffer oriented interface of +__basic_parser__ to build a stream operation which parses an HTTP message +from a `std::istream`: + +[example_http_read_istream] + +[tip + Parsing from a `std::istream` could be implemented using an alternate + strategy: adapt the `std::istream` interface to a __SyncReadStream__, + enabling use with the library's existing stream algorithms. This is + left as an exercise for the reader. +] + +[endsect] + + + +[endsect] diff --git a/doc/5_08_custom_body.qbk b/doc/5_08_custom_body.qbk new file mode 100644 index 0000000000..b209ac5d03 --- /dev/null +++ b/doc/5_08_custom_body.qbk @@ -0,0 +1,158 @@ +[/ + 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) +] + +[section Custom Body Types] +[block''''''] + +User-defined types are possible for the message body, where the type meets the +__Body__ requirements. This simplified class declaration +shows the customization points available to user-defined body types: +``` +/// Defines a Body type +struct body +{ + /// This determines the type of the `message::body` member + using value_type = ...; + + /// An optional function, returns the body's payload size + static + std::uint64_t + size(value_type const& v); + + /// The algorithm used for extracting buffers + class reader; + + /// The algorithm used for inserting buffers + class writer; +} +``` + +The meaning of the nested types is as follows + +[table Body Type Members +[[Name][Description]] +[ + [`value_type`] + [ + Determines the type of the + [link beast.ref.beast__http__message.body `message::body`] + member. + ] +][ + [`reader`] + [ + An optional nested type meeting the requirements of __BodyReader__, + which provides the algorithm for converting the body representation + to a forward range of buffer sequences. + If present this body type may be used with a __serializer__. + ] +][ + [`writer`] + [ + An optional nested type meeting the requirements of __BodyWriter__, + which provides the algorithm for storing a forward range of buffer + sequences in the body representation. + If present, this body type may be used with a __parser__. + ] +] +] + +[heading Value Type] + +The `value_type` nested type allows the body to define the declaration of +the body type as it appears in the message. This can be any type. For +example, a body's value type may specify `std::vector` or even +`std::list`. A custom body may even set the value type to +something that is not a container for body octets, such as a +[@http://www.boost.org/libs/filesystem/doc/reference.html#class-path `boost::filesystem::path`]. +Or, a more structured container may be chosen. This declares a body's +value type as a JSON tree structure produced from a +[@http://www.boost.org/doc/html/property_tree/parsers.html#property_tree.parsers.json_parser `json_parser`]: +``` +#include +#include + +struct Body +{ + using value_type = boost::property_tree::ptree; + + class reader; + + class writer; + + // Optional member + static + std::uint64_t + size(value_type const&); +}; +``` + +As long as a suitable reader or writer is available to provide the +algorithm for transferring buffers in and out of the value type, +those bodies may be serialized or parsed. + + + +[section File Body] + +Use of the flexible __Body__ concept customization point enables authors to +preserve the self-contained nature of the __message__ object while allowing +domain specific behaviors. Common operations for HTTP servers include sending +responses which deliver file contents, and allowing for file uploads. In this +example we build the `basic_file_body` type which supports both reading and +writing to a file on the file system. The interface is a class templated +on the type of file used to access the file system, which must meet the +requirements of __File__. + +First we declare the type with its nested types: + +[example_http_file_body_1] + +We will start with the definition of the `value_type`. Our strategy +will be to store the file object directly in the message container +through the `value_type` field. To use this body it will be necessary +to call `msg.body.file().open()` first with the required information +such as the path and open mode. This ensures that the file exists +throughout the operation and prevent the race condition where the +file is removed from the file system in between calls. + +[example_http_file_body_2] + +Our implementation of __BodyReader__ will contain a small buffer +from which the file contents are read. The buffer is provided to +the implementation on each call until everything has been read in. + +[example_http_file_body_3] + +And here are the definitions for the functions we have declared: + +[example_http_file_body_4] + +Files can be read now, and the next step is to allow writing to files +by implementing the __BodyWriter__. The style is similar to the reader, +except that buffers are incoming instead of outgoing. Here's the +declaration: + +[example_http_file_body_5] + +Finally, here is the implementation of the writer member functions: + +[example_http_file_body_6] + +We have created a full featured body type capable of reading and +writing files on the filesystem, integrating seamlessly with the +HTTP algorithms and message container. The body type works with +any file implementation meeting the requirements of __File__ so +it may be transparently used with solutions optimized for particular +platforms. Example HTTP servers which use file bodies are available +in the example directory. + +[endsect] + + + +[endsect] diff --git a/doc/5_09_custom_parsers.qbk b/doc/5_09_custom_parsers.qbk new file mode 100644 index 0000000000..610dc16739 --- /dev/null +++ b/doc/5_09_custom_parsers.qbk @@ -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) +] + +[section Custom Parsers] + +While the parsers included in the library will handle a broad number of +use-cases, the __basic_parser__ interface can be subclassed to implement +custom parsing strategies: the basic parser processes the incoming octets +into elements according to the HTTP/1 protocol specification, while the +derived class decides what to do with those elements. In particular, users +who create exotic containers for [*Fields] may need to also create their +own parser. Custom parsers will work with all of the stream read operations +that work on parsers, as those algorithms use only the basic parser +interface. Some use cases for implementing custom parsers are: + +* Inspect incoming header fields and keep or discard them. + +* Use a container provided by an external interface. + +* Store header data in a user-defined __Fields__ type. + +The basic parser uses the Curiously Recurring Template Pattern +([@https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern CRTP]). +To declare your user defined parser, derive it from __basic_parser__. +The interface to the parser is event-driven. Member functions of the derived +class (termed "callbacks" in this context) are invoked with parsed elements +as they become available, requiring either the `friend` declaration as shown +above or that the member functions are declared public (not recommended). +Buffers provided by the parser are non-owning references; it is the +responsibility of the derived class to copy any information it needs before +returning from the callback. + +[example_http_custom_parser] + +[endsect] diff --git a/doc/6_0_http_examples.qbk b/doc/6_0_http_examples.qbk new file mode 100644 index 0000000000..de7b50618f --- /dev/null +++ b/doc/6_0_http_examples.qbk @@ -0,0 +1,143 @@ +[/ + 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) +] + +[section More Examples] + +These examples in this section are working functions that may be found +in the examples directory. They demonstrate the usage of the library for +a variety of scenarios. + + + +[section Change Body Type] + +Sophisticated servers may wish to defer the choice of the Body template type +until after the header is available. Then, a body type may be chosen +depending on the header contents. For example, depending on the verb, +target path, or target query parameters. To accomplish this, a parser +is declared to read in the header only, using a trivial body type such as +[link beast.ref.beast__http__empty_body `empty_body`]. Then, a new parser is constructed +from this existing parser where the body type is conditionally determined +by information from the header or elsewhere. + +This example illustrates how a server may make the commitment of a body +type depending on the method verb: + +[example_http_defer_body] + +[endsect] + + + +[section Expect 100-continue (Client)] + +The Expect field with the value "100-continue" in a request is special. It +indicates that the after sending the message header, a client desires an +immediate informational response before sending the the message body, which +presumably may be expensive to compute or large. This behavior is described in +[@https://tools.ietf.org/html/rfc7231#section-5.1.1 rfc7231 section 5.1.1]. +Invoking the 100-continue behavior is implemented easily in a client by +constructing a __serializer__ to send the header first, then receiving +the server response, and finally conditionally send the body using the same +serializer instance. A synchronous, simplified version (no timeout) of +this client action looks like this: + +[example_http_send_expect_100_continue] + +[endsect] + + + +[section Expect 100-continue (Server)] + +The Expect field with the value "100-continue" in a request is special. It +indicates that the after sending the message header, a client desires an +immediate informational response before sending the the message body, which +presumably may be expensive to compute or large. This behavior is described in +[@https://tools.ietf.org/html/rfc7231#section-5.1.1 rfc7231 section 5.1.1]. +Handling the Expect field can be implemented easily in a server by constructing +a __parser__ to read the header first, then send an informational HTTP +response, and finally read the body using the same parser instance. A +synchronous version of this server action looks like this: + +[example_http_receive_expect_100_continue] + +[endsect] + + + +[section HEAD request (Client)] + +The +[@https://tools.ietf.org/html/rfc7231#section-4.3.2 HEAD request] +method indicates to the server that the client wishes to receive the +entire header that would be delivered if the method was GET, except +that the body is omitted. + +[example_http_do_head_request] + +[endsect] + + + +[section HEAD response (Server)] + +When a server receives a +[@https://tools.ietf.org/html/rfc7231#section-4.3.2 HEAD request], +the response should contain the entire header that would be delivered +if the method was GET, except that the body is omitted. + +[example_http_do_head_response] + +[endsect] + + + +[section HTTP Relay] + +An HTTP proxy acts as a relay between client and server. The proxy reads a +request from the client and sends it to the server, possibly adjusting some +of the headers and representation of the body along the way. Then, the +proxy reads a response from the server and sends it back to the client, +also with the possibility of changing the headers and body representation. + +The example that follows implements a synchronous HTTP relay. It uses a +fixed size buffer, to avoid reading in the entire body so that the upstream +connection sees a header without unnecessary latency. This example brings +together all of the concepts discussed so far, it uses both a __serializer__ +and a __parser__ to achieve its goal: + +[example_http_relay] + +[endsect] + + + +[section Send Child Process Output] + +Sometimes it is necessary to send a message whose body is not conveniently +described by a single container. For example, when implementing an HTTP relay +function a robust implementation needs to present body buffers individually +as they become available from the downstream host. These buffers should be +fixed in size, otherwise creating the unnecessary and inefficient burden of +reading the complete message body before forwarding it to the upstream host. + +To enable these use-cases, the body type __buffer_body__ is provided. This +body uses a caller-provided pointer and size instead of an owned container. +To use this body, instantiate an instance of the serializer and fill in +the pointer and size fields before calling a stream write function. + +This example reads from a child process and sends the output back in an +HTTP response. The output of the process is sent as it becomes available: + +[example_http_send_cgi_response] + +[endsect] + + + +[endsect] diff --git a/doc/7_0_websocket.qbk b/doc/7_0_websocket.qbk new file mode 100644 index 0000000000..7e2e03a5a3 --- /dev/null +++ b/doc/7_0_websocket.qbk @@ -0,0 +1,38 @@ +[/ + 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) +] + +[section Using WebSocket] + +The WebSocket Protocol enables two-way communication between a client +running untrusted code in a controlled environment to a remote host that has +opted-in to communications from that code. The protocol consists of an opening +handshake followed by basic message framing, layered over TCP. The goal of +this technology is to provide a mechanism for browser-based applications +needing two-way communication with servers without relying on opening multiple +HTTP connections. + +Beast provides developers with a robust WebSocket implementation built on +Boost.Asio with a consistent asynchronous model using a modern C++ approach. + +[note + This documentation assumes familiarity with __Asio__ and + the protocol specification described in __rfc6455__. + Sample code and identifiers appearing in this section is written + as if these declarations are in effect: + + [ws_snippet_1] +] + +[include 7_1_streams.qbk] +[include 7_2_connect.qbk] +[include 7_3_client.qbk] +[include 7_4_server.qbk] +[include 7_5_messages.qbk] +[include 7_6_control.qbk] +[include 7_7_notes.qbk] + +[endsect] diff --git a/doc/7_1_streams.qbk b/doc/7_1_streams.qbk new file mode 100644 index 0000000000..002f09321a --- /dev/null +++ b/doc/7_1_streams.qbk @@ -0,0 +1,64 @@ +[/ + 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) +] + +[section Creating Streams] + +The interface to the WebSocket implementation is a single template class +[link beast.ref.beast__websocket__stream `stream`] +which wraps an existing network transport object or other type of +octet oriented stream. The wrapped object is called the "next layer" +and must meet the requirements of __SyncStream__ if synchronous +operations are performed, __AsyncStream__ if asynchronous operations +are performed, or both. Any arguments supplied during construction of +the stream wrapper are passed to next layer's constructor. + +Here we declare a websocket stream over a TCP/IP socket with ownership +of the socket. The `io_service` argument is forwarded to the wrapped +socket's constructor: + +[ws_snippet_2] + +[heading Using SSL] + +To use WebSockets over SSL, use an instance of the `boost::asio::ssl::stream` +class template as the template type for the stream. The required `io_service` +and `ssl::context` arguments are forwarded to the wrapped stream's constructor: + +[wss_snippet_1] +[wss_snippet_2] + +[important + Code which declares websocket stream objects using Asio SSL types + must include the file [include_file beast/websocket/ssl.hpp]. +] + +[heading Non-owning References] + +If a socket type supports move construction, a websocket stream may be +constructed around the already existing socket by invoking the move +constructor signature: + +[ws_snippet_3] + +Or, the wrapper can be constructed with a non-owning reference. In +this case, the caller is responsible for managing the lifetime of the +underlying socket being wrapped: + +[ws_snippet_4] + +Once the WebSocket stream wrapper is created, the wrapped object may be +accessed by calling +[link beast.ref.beast__websocket__stream.next_layer.overload1 `stream::next_layer`]: + +[ws_snippet_5] + +[warning + Initiating operations on the next layer while websocket + operations are being performed may result in undefined behavior. +] + +[endsect] diff --git a/doc/7_2_connect.qbk b/doc/7_2_connect.qbk new file mode 100644 index 0000000000..a7f8f233a5 --- /dev/null +++ b/doc/7_2_connect.qbk @@ -0,0 +1,32 @@ +[/ + 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) +] + +[section Establishing Connections] + +Connections are established by invoking functions directly on the next layer +object. For example, to make an outgoing connection using a standard TCP/IP +socket: + +[ws_snippet_6] + +Similarly, to accept an incoming connection using a standard TCP/IP +socket, pass the next layer object to the acceptor: + +[ws_snippet_7] + +When using SSL, which itself wraps a next layer object that is usually a +TCP/IP socket, multiple calls to retrieve the next layer may be required. +In this example, the websocket stream wraps the SSL stream which wraps +the TCP/IP socket: + +[wss_snippet_3] + +[note + Examples use synchronous interfaces for clarity of exposition. +] + +[endsect] diff --git a/doc/7_3_client.qbk b/doc/7_3_client.qbk new file mode 100644 index 0000000000..65a0e651c4 --- /dev/null +++ b/doc/7_3_client.qbk @@ -0,0 +1,95 @@ +[/ + 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) +] + +[section Handshaking (Clients)] + +A WebSocket session begins when a client sends the HTTP/1 +[@https://tools.ietf.org/html/rfc7230#section-6.7 Upgrade] +request for +[@https://tools.ietf.org/html/rfc6455#section-1.3 websocket], +and the server sends an appropriate response indicating that +the request was accepted and that the connection has been upgraded. +The Upgrade request must include the +[@https://tools.ietf.org/html/rfc7230#section-5.4 Host] +field, and the +[@https://tools.ietf.org/html/rfc7230#section-5.3 target] +of the resource to request. The stream member functions +[link beast.ref.beast__websocket__stream.handshake.overload1 `handshake`] and +[link beast.ref.beast__websocket__stream.async_handshake.overload1 `async_handshake`] +are used to send the request with the required host and target strings. + +[ws_snippet_8] + +The implementation will create and send a request that typically +looks like this: + +[table WebSocket Upgrade HTTP Request +[[Serialized Octets][Description]] +[[ +``` + GET / HTTP/1.1 + Host: localhost + Upgrade: websocket + Connection: upgrade + Sec-WebSocket-Key: 2pGeTR0DsE4dfZs2pH+8MA== + Sec-WebSocket-Version: 13 + User-Agent: Beast +``` +][ + The host and target parameters become part of the Host field + and request-target in the resulting HTTP request. The key is + generated by the implementation. Callers may add fields or + modify fields by providing a ['decorator], described below. +]]] + +[heading Decorators] + +If the caller wishes to add or modify fields, the member functions +[link beast.ref.beast__websocket__stream.handshake_ex `handshake_ex`] and +[link beast.ref.beast__websocket__stream.async_handshake_ex `async_handshake_ex`] +are provided which allow an additional function object, called a +['decorator], to be passed. The decorator is invoked to modify +the HTTP Upgrade request as needed. This example sets a subprotocol +on the request: + +[ws_snippet_9] + +The HTTP Upgrade request produced by the previous call will look thusly: + +[table Decorated WebSocket Upgrade HTTP Request +[[Serialized Octets][Description]] +[[ + ``` + GET / HTTP/1.1 + Host: localhost + Upgrade: websocket + Connection: upgrade + Sec-WebSocket-Key: 2pGeTR0DsE4dfZs2pH+8MA== + Sec-WebSocket-Version: 13 + Sec-WebSocket-Protocol: xmpp;ws-chat + User-Agent: Beast + ``` +][ + Undefined behavior results if the decorator modifies the fields + specific to perform the WebSocket Upgrade , such as the Upgrade + and Connection fields. +]]] + +[heading Filtering] + +When a client receives an HTTP Upgrade response from the server indicating +a successful upgrade, the caller may wish to perform additional validation +on the received HTTP response message. For example, to check that the +response to a basic authentication challenge is valid. To achieve this, +overloads of the handshake member function allow the caller to store the +received HTTP message in an output reference argument as +[link beast.ref.beast__websocket__response_type `response_type`] +as follows: + +[ws_snippet_10] + +[endsect] diff --git a/doc/7_4_server.qbk b/doc/7_4_server.qbk new file mode 100644 index 0000000000..f32b3105e1 --- /dev/null +++ b/doc/7_4_server.qbk @@ -0,0 +1,116 @@ +[/ + 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) +] + +[section Handshaking (Servers)] + +A +[link beast.ref.beast__websocket__stream `stream`] +automatically handles receiving and processing the HTTP response to the +handshake request. The call to handshake is successful if a HTTP response +is received with the 101 "Switching Protocols" status code. On failure, +an error is returned or an exception is thrown. Depending on the keep alive +setting, the connection may remain open for a subsequent handshake attempt. + +Performing a handshake for an incoming websocket upgrade request operates +similarly. If the handshake fails, an error is returned or exception thrown: + +[ws_snippet_11] + +Successful WebSocket Upgrade responses generated by the implementation will +typically look like this: + +[table Decorated WebSocket Upgrade HTTP Request +[[Serialized Octets][Description]] +[[ + ``` + HTTP/1.1 101 Switching Protocols + Upgrade: websocket + Connection: upgrade + Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= + Server: Beast/40 + ``` +][ + The Sec-WebSocket-Accept field value is generated from the + request in a fashion specified by the WebSocket protocol. +]]] + +[heading Decorators] + +If the caller wishes to add or modify fields, the member functions +[link beast.ref.beast__websocket__stream.accept_ex `accept_ex`] and +[link beast.ref.beast__websocket__stream.async_accept_ex `async_accept_ex`] +are provided which allow an additional function object, called a +['decorator], to be passed. The decorator is invoked to modify +the HTTP Upgrade request as needed. This example sets the Server +field on the response: + +[ws_snippet_12] + +The HTTP Upgrade response produced by the previous call looks like this: + +[table Decorated WebSocket Upgrade HTTP Request +[[Serialized Octets][Description]] +[[ + ``` + HTTP/1.1 101 Switching Protocols + Upgrade: websocket + Connection: upgrade + Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= + Server: AcmeServer + ``` +][ + When the Upgrade request fails, the implementation will still invoke + the decorator to modify the response. In this case, the response + object will have a status code other than 101. + + Undefined behavior results when the upgrade request is successful + and the decorator modifies the fields specific to perform the + WebSocket Upgrade, such as the Upgrade and Connection fields. +]]] + +[heading Passing HTTP Requests] + +When implementing an HTTP server that also supports WebSocket, the +server usually reads the HTTP request from the client. To detect when +the incoming HTTP request is a WebSocket Upgrade request, the function +[link beast.ref.beast__websocket__is_upgrade `is_upgrade`] may be used. + +Once the caller determines that the HTTP request is a WebSocket Upgrade, +additional overloads of +[link beast.ref.beast__websocket__stream.accept `accept`], +[link beast.ref.beast__websocket__stream.accept_ex `accept_ex`], +[link beast.ref.beast__websocket__stream.async_accept `async_accept`], and +[link beast.ref.beast__websocket__stream.async_accept_ex `async_accept_ex`] +are provided which receive the entire HTTP request header as an object +to perform the handshake. In this example, the request is first read +in using the HTTP algorithms, and then passed to a newly constructed +stream: + +[ws_snippet_13] + +[heading Buffered Handshakes] + +Sometimes a server implementation wishes to read octets on the stream +in order to route the incoming request. For example, a server may read +the first 6 octets after accepting an incoming connection to determine +if a TLS protocol is being negotiated, and choose a suitable implementation +at run-time. In the case where the server wishes to accept the incoming +request as an HTTP WebSocket Upgrade request, additional overloads of +[link beast.ref.beast__websocket__stream.accept `accept`], +[link beast.ref.beast__websocket__stream.accept_ex `accept_ex`], +[link beast.ref.beast__websocket__stream.async_accept `async_accept`], and +[link beast.ref.beast__websocket__stream.async_accept_ex `async_accept_ex`] +are provided which receive the additional buffered octets and consume +them as part of the handshake. + +In this example, the server reads the initial HTTP message into the +specified dynamic buffer as an octet sequence in the buffer's output +area, and later uses those octets to attempt an HTTP WebSocket Upgrade: + +[ws_snippet_14] + +[endsect] diff --git a/doc/7_5_messages.qbk b/doc/7_5_messages.qbk new file mode 100644 index 0000000000..9518d37a64 --- /dev/null +++ b/doc/7_5_messages.qbk @@ -0,0 +1,36 @@ +[/ + 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) +] + +[section Send and Receive Messages] + +After the WebSocket handshake is accomplished, callers may send and receive +messages using the message oriented interface. This interface requires that +all of the buffers representing the message are known ahead of time: + +[ws_snippet_15] + +[important + Calls to [link beast.ref.beast__websocket__stream.set_option `set_option`] + must be made from the same implicit or explicit strand as that used + to perform other operations. +] + +[heading Frames] + +Some use-cases make it impractical or impossible to buffer the entire +message ahead of time: + +* Streaming multimedia to an endpoint. +* Sending a message that does not fit in memory at once. +* Providing incremental results as they become available. + +For these cases, the frame oriented interface may be used. This +example reads and echoes a complete message using this interface: + +[ws_snippet_16] + +[endsect] diff --git a/doc/7_6_control.qbk b/doc/7_6_control.qbk new file mode 100644 index 0000000000..fb1de84dcb --- /dev/null +++ b/doc/7_6_control.qbk @@ -0,0 +1,113 @@ +[/ + 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) +] + +[section Control Frames] + +Control frames are small (less than 128 bytes) messages entirely contained +in an individual WebSocket frame. They may be sent at any time by either +peer on an established connection, and can appear in between continuation +frames for a message. There are three types of control frames: ping, pong, +and close. + +A sent ping indicates a request that the sender wants to receive a pong. A +pong is a response to a ping. Pongs may be sent unsolicited, at any time. +One use for an unsolicited pong is to inform the remote peer that the +session is still active after a long period of inactivity. A close frame +indicates that the remote peer wishes to close the WebSocket connection. +The connection is considered gracefully closed when each side has sent +and received a close frame. + +During read operations, Beast automatically reads and processes control +frames. If a control callback is registered, the callback is notified of +the incoming control frame. The implementation will respond to pings +automatically. The receipt of a close frame initiates the WebSocket +close procedure, eventually resulting in the error code +[link beast.ref.beast__websocket__error `error::closed`] +being delivered to the caller in a subsequent read operation, assuming +no other error takes place. + +A consequence of this automatic behavior is that caller-initiated read +operations can cause socket writes. However, these writes will not +compete with caller-initiated write operations. For the purposes of +correctness with respect to the stream invariants, caller-initiated +read operations still only count as a read. This means that callers can +have a simultaneously active read, write, and ping/pong operation in +progress, while the implementation also automatically handles control +frames. + +[heading Control Callback] + +Ping, pong, and close messages are control frames which may be sent at +any time by either peer on an established WebSocket connection. They +are sent using the functions +[link beast.ref.beast__websocket__stream.ping `ping`], +[link beast.ref.beast__websocket__stream.pong `pong`]. +and +[link beast.ref.beast__websocket__stream.close `close`]. +To be notified of control frames, callers may register a +['control callback] using +[link beast.ref.beast__websocket__stream.control_callback `control_callback`]. +The object provided with this option should be callable with the following +signature: + +[ws_snippet_17] + +When a control callback is registered, it will be invoked for all pings, +pongs, and close frames received through either synchronous read functions +or asynchronous read functions. The type of frame and payload text are +passed as parameters to the control callback. If the frame is a close +frame, the close reason may be obtained by calling +[link beast.ref.beast__websocket__stream.reason `reason`]. + +Unlike regular completion handlers used in calls to asynchronous initiation +functions, the control callback only needs to be set once. The callback is +not reset after being called. The same callback is used for both synchronous +and asynchronous reads. The callback is passive; in order to be called, +a stream read operation must be active. + +[note + When an asynchronous read function receives a control frame, the + control callback is invoked in the same manner as that used to + invoke the final completion handler of the corresponding read + function. +] + +[heading Close Frames] + +The WebSocket protocol defines a procedure and control message for initiating +a close of the session. Handling of close initiated by the remote end of the +connection is performed automatically. To manually initiate a close, use +the +[link beast.ref.beast__websocket__stream.close `close`] function: + +[ws_snippet_18] + +When the remote peer initiates a close by sending a close frame, Beast +will handle it for you by causing the next read to return `error::closed`. +When this error code is delivered, it indicates to the application that +the WebSocket connection has been closed cleanly, and that the TCP/IP +connection has been closed. After initiating a close, it is necessary to +continue reading messages until receiving the error `error::closed`. This +is because the remote peer may still be sending message and control frames +before it receives and responds to the close frame. + +[important + To receive the + [link beast.ref.beast__websocket__error `error::closed`] + error, a read operation is required. +] + +[heading Auto-fragment] + +To ensure timely delivery of control frames, large messages can be broken up +into smaller sized frames. The automatic fragment option turns on this +feature, and the write buffer size option determines the maximum size of +the fragments: + +[ws_snippet_19] + +[endsect] diff --git a/doc/7_7_notes.qbk b/doc/7_7_notes.qbk new file mode 100644 index 0000000000..2444072c8d --- /dev/null +++ b/doc/7_7_notes.qbk @@ -0,0 +1,55 @@ +[/ + 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) +] + +[section Notes] + +Because calls to read data may return a variable amount of bytes, the +interface to calls that read data require an object that meets the requirements +of __DynamicBuffer__. This concept is modeled on __streambuf__. + +The implementation does not perform queueing or buffering of messages. If +desired, these features should be provided by callers. The impact of this +design is that library users are in full control of the allocation strategy +used to store data and the back-pressure applied on the read and write side +of the underlying TCP/IP connection. + +[heading Asynchronous Operations] + +Asynchronous versions are available for all functions: + +[ws_snippet_20] + +Calls to asynchronous initiation functions support the extensible asynchronous +model developed by the Boost.Asio author, allowing for traditional completion +handlers, stackful or stackless coroutines, and even futures: + +[ws_snippet_21] + +[heading The io_service] + +The creation and operation of the __io_service__ associated with the +underlying stream is left to the callers, permitting any implementation +strategy including one that does not require threads for environments +where threads are unavailable. Beast WebSocket itself does not use +or require threads. + +[heading Thread Safety] + +Like a regular __Asio__ socket, a +[link beast.ref.beast__websocket__stream `stream`] +is not thread safe. Callers are responsible for synchronizing operations on +the socket using an implicit or explicit strand, as per the Asio documentation. +The asynchronous interface supports one active read and one active write +simultaneously. Undefined behavior results if two or more reads or two or +more writes are attempted concurrently. Caller initiated WebSocket ping, pong, +and close operations each count as an active write. + +The implementation uses composed asynchronous operations internally; a high +level read can cause both reads and writes to take place on the underlying +stream. This behavior is transparent to callers. + +[endsect] diff --git a/doc/8_concepts.qbk b/doc/8_concepts.qbk new file mode 100644 index 0000000000..7d1f23831b --- /dev/null +++ b/doc/8_concepts.qbk @@ -0,0 +1,22 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:concept Concepts] + +This section describes all of the concepts defined by the library. + +[include concept/Body.qbk] +[include concept/BodyReader.qbk] +[include concept/BodyWriter.qbk] +[include concept/BufferSequence.qbk] +[include concept/DynamicBuffer.qbk] +[include concept/Fields.qbk] +[include concept/FieldsReader.qbk] +[include concept/File.qbk] +[include concept/Streams.qbk] + +[endsect] diff --git a/doc/9_0_design.qbk b/doc/9_0_design.qbk new file mode 100644 index 0000000000..8b4c0a0dfc --- /dev/null +++ b/doc/9_0_design.qbk @@ -0,0 +1,58 @@ +[/ + 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) +] + +[section Design Choices] + +The implementations were originally driven by business needs of cryptocurrency +server applications (e.g. [@https://github.com/ripple/rippled rippled]), +written in C++. These needs were not met by existing solutions so Beast +was written from scratch as a solution. Beast's design philosophy avoids +flaws exhibited by other libraries: + +* Don't try to do too much. + +* Don't sacrifice performance. + +* Mimic __Asio__; familiarity breeds confidence. + +* Role-symmetric interfaces; client and server the same (or close to it). + +* Leave important decisions, such as allocating memory or + managing flow control, to the user. + +Beast uses the __DynamicBuffer__ concept presented in the Networking TS +(__N4588__), and relies heavily on the __ConstBufferSequence__ and +__MutableBufferSequence__ concepts for passing buffers to functions. +The authors have found the dynamic buffer and buffer sequence interfaces to +be optimal for interacting with Asio, and for other tasks such as incremental +parsing of data in buffers (for example, parsing websocket frames stored +in a [link beast.ref.beast__static_buffer `static_buffer`]). + +During the development of Beast the authors have studied other software +packages and in particular the comments left during the Boost Review process +of other packages offering similar functionality. In this section and the +FAQs that follow we attempt to answer those questions that are also applicable +to Beast. + +For HTTP we model the message to maximize flexibility of implementation +strategies while allowing familiar verbs such as [*`read`] and [*`write`]. +The HTTP interface is further driven by the needs of the WebSocket module, +as a WebSocket session requires a HTTP Upgrade handshake exchange at the +start. Other design goals: + +* Keep it simple. + +* Stay low level; don't invent a whole web server or client. + +* Allow for customizations, if the user needs it. + +[include 9_1_http_message.qbk] +[include 9_2_http_comparison.qbk] +[include 9_3_websocket_zaphoyd.qbk] +[include 9_4_faq.qbk] + +[endsect] diff --git a/doc/9_1_http_message.qbk b/doc/9_1_http_message.qbk new file mode 100644 index 0000000000..af1f36c67c --- /dev/null +++ b/doc/9_1_http_message.qbk @@ -0,0 +1,340 @@ +[/ + 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) +] + +[section HTTP Message Container] + +In this section we describe the problem of modeling HTTP messages and explain +how the library arrived at its solution, with a discussion of the benefits +and drawbacks of the design choices. The goal for creating a message model +is to create a container with value semantics, possibly movable and/or +copyable, that contains all the information needed to serialize, or all +of the information captured during parsing. More formally, given: + +* `m` is an instance of an HTTP message container + +* `x` is a series of octets describing a valid HTTP message in + the serialized format described in __rfc7230__. + +* `S(m)` is a serialization function which produces a series of octets + from a message container. + +* `P(x)` is a parsing function which produces a message container from + a series of octets. + +These relations are true: + +* `S(m) == x` + +* `P(S(m)) == m` + +We would also like our message container to have customization points +permitting the following: allocator awareness, user-defined containers +to represent header fields, and user-defined types and algorithms to +represent the body. And finally, because requests and responses have +different fields in the ['start-line], we would like the containers for +requests and responses to be represented by different types for function +overloading. + +Here is our first attempt at declaring some message containers: + +[table +[[ +``` +/// An HTTP request +template +struct request +{ + int version; + std::string method; + std::string target; + Fields fields; + + typename Body::value_type body; +}; +``` +][ +``` +/// An HTTP response +template +struct response +{ + int version; + int status; + std::string reason; + Fields fields; + + typename Body::value_type body; +}; +``` +]] +] + +These containers are capable of representing everything in the model +of HTTP requests and responses described in __rfc7230__. Request and +response objects are different types. The user can choose the container +used to represent the fields. And the user can choose the [*Body] type, +which is a concept defining not only the type of `body` member but also +the algorithms used to transfer information in and out of that member +when performing serialization and parsing. + +However, a problem arises. How do we write a function which can accept +an object that is either a request or a response? As written, the only +obvious solution is to make the message a template type. Additional traits +classes would then be needed to make sure that the passed object has a +valid type which meets the requirements. These unnecessary complexities +are bypassed by making each container a partial specialization: +``` +/// An HTTP message +template +struct message; + +/// An HTTP request +template +struct message +{ + int version; + std::string method; + std::string target; + Fields fields; + + typename Body::value_type body; +}; + +/// An HTTP response +template +struct message +{ + int version; + int status; + std::string reason; + Fields fields; + + typename Body::value_type body; +}; +``` + +Now we can declare a function which takes any message as a parameter: +``` +template +void f(message& msg); +``` + +This function can manipulate the fields common to requests and responses. +If it needs to access the other fields, it can use overloads with +partial specialization, or in C++17 a `constexpr` expression: +``` +template +void f(message& msg) +{ + if constexpr(isRequest) + { + // call msg.method(), msg.target() + } + else + { + // call msg.result(), msg.reason() + } +} +``` + +Often, in non-trivial HTTP applications, we want to read the HTTP header +and examine its contents before choosing a type for [*Body]. To accomplish +this, there needs to be a way to model the header portion of a message. +And we'd like to do this in a way that allows functions which take the +header as a parameter, to also accept a type representing the whole +message (the function will see just the header part). This suggests +inheritance, by splitting a new base class off of the message: +``` +/// An HTTP message header +template +struct header; +``` + +Code which accesses the fields has to laboriously mention the `fields` +member, so we'll not only make `header` a base class but we'll make +a quality of life improvement and derive the header from the fields +for notational convenience. In order to properly support all forms +of construction of [*Fields] there will need to be a set of suitable +constructor overloads (not shown): +``` +/// An HTTP request header +template +struct header : Fields +{ + int version; + std::string method; + std::string target; +}; + +/// An HTTP response header +template +struct header : Fields +{ + int version; + int status; + std::string reason; +}; + +/// An HTTP message +template +struct message : header +{ + typename Body::value_type body; + + /// Construct from a `header` + message(header&& h); +}; + +``` + +Note that the `message` class now has a constructor allowing messages +to be constructed from a similarly typed `header`. This handles the case +where the user already has the header and wants to make a commitment to the +type for [*Body]. A function can be declared which accepts any header: +``` +template +void f(header& msg); +``` + +Until now we have not given significant consideration to the constructors +of the `message` class. But to achieve all our goals we will need to make +sure that there are enough constructor overloads to not only provide for +the special copy and move members if the instantiated types support it, +but also allow the fields container and body container to be constructed +with arbitrary variadic lists of parameters. This allows the container +to fully support allocators. + +The solution used in the library is to treat the message like a `std::pair` +for the purposes of construction, except that instead of `first` and `second` +we have the `Fields` base class and `message::body` member. This means that +single-argument constructors for those fields should be accessible as they +are with `std::pair`, and that a mechanism identical to the pair's use of +`std::piecewise_construct` should be provided. Those constructors are too +complex to repeat here, but interested readers can view the declarations +in the corresponding header file. + +There is now significant progress with our message container but a stumbling +block remains. There is no way to control the allocator for the `std::string` +members. We could add an allocator to the template parameter list of the +header and message classes, use it for those strings. This is unsatisfying +because of the combinatorial explosion of constructor variations needed to +support the scheme. It also means that request messages could have [*four] +different allocators: two for the fields and body, and two for the method +and target strings. A better solution is needed. + +To get around this we make an interface modification and then add +a requirement to the [*Fields] type. First, the interface change: +``` +/// An HTTP request header +template +struct header : Fields +{ + int version; + + verb method() const; + string_view method_string() const; + void method(verb); + void method(string_view); + + string_view target(); const; + void target(string_view); + +private: + verb method_; +}; + +/// An HTTP response header +template +struct header : Fields +{ + int version; + int result; + string_view reason() const; + void reason(string_view); +}; +``` + +The start-line data members are replaced traditional accessors using +non-owning references to string buffers. The method is stored using +a simple integer instead of the entire string, for the case where +the method is recognized from the set of known verb strings. + +Now we add a requirement to the fields type: management of the +corresponding string is delegated to the [*Fields] container, which can +already be allocator aware and constructed with the necessary allocator +parameter via the provided constructor overloads for `message`. The +delegation implementation looks like this (only the response header +specialization is shown): +``` +/// An HTTP response header +template +struct header : Fields +{ + int version; + int status; + + string_view + reason() const + { + return this->reason_impl(); // protected member of Fields + } + + void + reason(string_view s) + { + this->reason_impl(s); // protected member of Fields + } +}; +``` + +Now that we've accomplished our initial goals and more, there are a few +more quality of life improvements to make. Users will choose different +types for `Body` far more often than they will for `Fields`. Thus, we +swap the order of these types and provide a default. Then, we provide +type aliases for requests and responses to soften the impact of using +`bool` to choose the specialization: + +``` +/// An HTTP header +template +struct header; + +/// An HTTP message +template +struct message; + +/// An HTTP request +template +using request = message; + +/// An HTTP response +template +using response = message; +``` + +This allows concise specification for the common cases, while +allowing for maximum customization for edge cases: +``` +request req; + +response res; +``` + +This container is also capable of representing complete HTTP/2 messages. +Not because it was explicitly designed for, but because the IETF wanted to +preserve message compatibility with HTTP/1. Aside from version specific +fields such as Connection, the contents of HTTP/1 and HTTP/2 messages are +identical even though their serialized representation is considerably +different. The message model presented in this library is ready for HTTP/2. + +In conclusion, this representation for the message container is well thought +out, provides comprehensive flexibility, and avoids the necessity of defining +additional traits classes. User declarations of functions that accept headers +or messages as parameters are easy to write in a variety of ways to accomplish +different results, without forcing cumbersome SFINAE declarations everywhere. + +[endsect] diff --git a/doc/9_2_http_comparison.qbk b/doc/9_2_http_comparison.qbk new file mode 100644 index 0000000000..e181fcb51a --- /dev/null +++ b/doc/9_2_http_comparison.qbk @@ -0,0 +1,454 @@ +[/ + 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) +] + +[section HTTP Comparison to Other Libraries] + +There are a few C++ published libraries which implement some of the HTTP +protocol. We analyze the message model chosen by those libraries and discuss +the advantages and disadvantages relative to Beast. + +The general strategy used by the author to evaluate external libraries is +as follows: + +* Review the message model. Can it represent a complete request or + response? What level of allocator support is present? How much + customization is possible? + +* Review the stream abstraction. This is the type of object, such as + a socket, which may be used to parse or serialize (i.e. read and write). + Can user defined types be specified? What's the level of conformance to + to Asio or Networking-TS concepts? + +* Check treatment of buffers. Does the library manage the buffers + or can users provide their own buffers? + +* How does the library handle corner cases such as trailers, + Expect: 100-continue, or deferred commitment of the body type? + +[note + Declarations examples from external libraries have been edited: + portions have been removed for simplification. +] + + + +[heading cpp-netlib] + +[@https://github.com/cpp-netlib/cpp-netlib/tree/092cd570fb179d029d1865aade9f25aae90d97b9 [*cpp-netlib]] +is a network programming library previously intended for Boost but not +having gone through formal review. As of this writing it still uses the +Boost name, namespace, and directory structure although the project states +that Boost acceptance is no longer a goal. The library is based on Boost.Asio +and bills itself as ['"a collection of network related routines/implementations +geared towards providing a robust cross-platform networking library"]. It +cites ['"Common Message Type"] as a feature. As of the branch previous +linked, it uses these declarations: +``` +template +struct basic_message { + public: + typedef Tag tag; + + typedef typename headers_container::type headers_container_type; + typedef typename headers_container_type::value_type header_type; + typedef typename string::type string_type; + + headers_container_type& headers() { return headers_; } + headers_container_type const& headers() const { return headers_; } + + string_type& body() { return body_; } + string_type const& body() const { return body_; } + + string_type& source() { return source_; } + string_type const& source() const { return source_; } + + string_type& destination() { return destination_; } + string_type const& destination() const { return destination_; } + + private: + friend struct detail::directive_base; + friend struct detail::wrapper_base >; + + mutable headers_container_type headers_; + mutable string_type body_; + mutable string_type source_; + mutable string_type destination_; +}; +``` + +This container is the base class template used to represent HTTP messages. +It uses a "tag" type style specializations for a variety of trait classes, +allowing for customization of the various parts of the message. For example, +a user specializes `headers_container` to determine what container type +holds the header fields. We note some problems with the container declaration: + +* The header and body containers may only be default-constructed. + +* No stateful allocator support. + +* There is no way to defer the commitment of the type for `body_` to + after the headers are read in. + +* The message model includes a "source" and "destination." This is + extraneous metadata associated with the connection which is not part + of the HTTP protocol specification and belongs elsewhere. + +* The use of `string_type` (a customization point) for source, + destination, and body suggests that `string_type` models a + [*ForwardRange] whose `value_type` is `char`. This representation + is less than ideal, considering that the library is built on + Boost.Asio. Adapting a __DynamicBuffer__ to the required forward + range destroys information conveyed by the __ConstBufferSequence__ + and __MutableBufferSequence__ used in dynamic buffers. The consequence + is that cpp-netlib implementations will be less efficient than an + equivalent __N4588__ conforming implementation. + +* The library uses specializations of `string` to change the type + of string used everywhere, including the body, field name and value + pairs, and extraneous metadata such as source and destination. The + user may only choose a single type: field name, field values, and + the body container will all use the same string type. This limits + utility of the customization point. The library's use of the string + trait is limited to selecting between `std::string` and `std::wstring`. + We do not find this use-case compelling given the limitations. + +* The specialized trait classes generate a proliferation of small + additional framework types. To specialize traits, users need to exit + their namespace and intrude into the `boost::network::http` namespace. + The way the traits are used in the library limits the usefulness + of the traits to trivial purpose. + +* The `string customization point constrains user defined body types + to few possible strategies. There is no way to represent an HTTP message + body as a filename with accompanying algorithms to store or retrieve data + from the file system. + +The design of the message container in this library is cumbersome +with its system of customization using trait specializations. The +use of these customizations is extremely limited due to the way they +are used in the container declaration, making the design overly +complex without corresponding benefit. + + + +[heading Boost.HTTP] + +[@https://github.com/BoostGSoC14/boost.http/tree/45fc1aa828a9e3810b8d87e669b7f60ec100bff4 [*boost.http]] +is a library resulting from the 2014 Google Summer of Code. It was submitted +for a Boost formal review and rejected in 2015. It is based on Boost.Asio, +and development on the library has continued to the present. As of the branch +previously linked, it uses these message declarations: +``` +template +struct basic_message +{ + typedef Headers headers_type; + typedef Body body_type; + + headers_type &headers(); + + const headers_type &headers() const; + + body_type &body(); + + const body_type &body() const; + + headers_type &trailers(); + + const headers_type &trailers() const; + +private: + headers_type headers_; + body_type body_; + headers_type trailers_; +}; + +typedef basic_message> message; + +template +struct is_message>: public std::true_type {}; +``` + +* This container cannot model a complete message. The ['start-line] items + (method and target for requests, reason-phrase for responses) are + communicated out of band, as is the ['http-version]. A function that + operates on the message including the start line requires additional + parameters. This is evident in one of the + [@https://github.com/BoostGSoC14/boost.http/blob/45fc1aa828a9e3810b8d87e669b7f60ec100bff4/example/basic_router.cpp#L81 example programs]. + The `500` and `"OK"` arguments represent the response ['status-code] and + ['reason-phrase] respectively: + ``` + ... + http::message reply; + ... + self->socket.async_write_response(500, string_ref("OK"), reply, yield); + ``` + +* `headers_`, `body_`, and `trailers_` may only be default-constructed, + since there are no explicitly declared constructors. + +* There is no way to defer the commitment of the [*Body] type to after + the headers are read in. This is related to the previous limitation + on default-construction. + +* No stateful allocator support. This follows from the previous limitation + on default-construction. Buffers for start-line strings must be + managed externally from the message object since they are not members. + +* The trailers are stored in a separate object. Aside from the combinatorial + explosion of the number of additional constructors necessary to fully + support arbitrary forwarded parameter lists for each of the headers, body, + and trailers members, the requirement to know in advance whether a + particular HTTP field will be located in the headers or the trailers + poses an unnecessary complication for general purpose functions that + operate on messages. + +* The declarations imply that `std::vector` is a model of [*Body]. + More formally, that a body is represented by the [*ForwardRange] + concept whose `value_type` is an 8-bit integer. This representation + is less than ideal, considering that the library is built on + Boost.Asio. Adapting a __DynamicBuffer__ to the required forward range + destroys information conveyed by the __ConstBufferSequence__ and + __MutableBufferSequence__ used in dynamic buffers. The consequence is + that Boost.HTTP implementations will be less efficient when dealing + with body containers than an equivalent __N4588__ conforming + implementation. + +* The [*Body] customization point constrains user defined types to + very limited implementation strategies. For example, there is no way + to represent an HTTP message body as a filename with accompanying + algorithms to store or retrieve data from the file system. + +This representation addresses a narrow range of use cases. It has +limited potential for customization and performance. It is more difficult +to use because it excludes the start line fields from the model. + + + +[heading C++ REST SDK (cpprestsdk)] + +[@https://github.com/Microsoft/cpprestsdk/tree/381f5aa92d0dfb59e37c0c47b4d3771d8024e09a [*cpprestsdk]] +is a Microsoft project which ['"...aims to help C++ developers connect to and +interact with services"]. It offers the most functionality of the libraries +reviewed here, including support for Websocket services using its websocket++ +dependency. It can use native APIs such as HTTP.SYS when building Windows +based applications, and it can use Boost.Asio. The WebSocket module uses +Boost.Asio exclusively. + +As cpprestsdk is developed by a large corporation, it contains quite a bit +of functionality and necessarily has more interfaces. We will break down +the interfaces used to model messages into more manageable pieces. This +is the container used to store the HTTP header fields: +``` +class http_headers +{ +public: + ... + +private: + std::map m_headers; +}; +``` + +This declaration is quite bare-bones. We note the typical problems of +most field containers: + +* The container may only be default-constructed. + +* No support for allocators, stateful or otherwise. + +* There are no customization points at all. + +Now we analyze the structure of +the larger message container. The library uses a handle/body idiom. There +are two public message container interfaces, one for requests (`http_request`) +and one for responses (`http_response`). Each interface maintains a private +shared pointer to an implementation class. Public member function calls +are routed to the internal implementation. This is the first implementation +class, which forms the base class for both the request and response +implementations: +``` +namespace details { + +class http_msg_base +{ +public: + http_headers &headers() { return m_headers; } + + _ASYNCRTIMP void set_body(const concurrency::streams::istream &instream, const utf8string &contentType); + + /// Set the stream through which the message body could be read + void set_instream(const concurrency::streams::istream &instream) { m_inStream = instream; } + + /// Set the stream through which the message body could be written + void set_outstream(const concurrency::streams::ostream &outstream, bool is_default) { m_outStream = outstream; m_default_outstream = is_default; } + + const pplx::task_completion_event & _get_data_available() const { return m_data_available; } + +protected: + /// Stream to read the message body. + concurrency::streams::istream m_inStream; + + /// stream to write the msg body + concurrency::streams::ostream m_outStream; + + http_headers m_headers; + bool m_default_outstream; + + /// The TCE is used to signal the availability of the message body. + pplx::task_completion_event m_data_available; +}; +``` + +To understand these declarations we need to first understand that cpprestsdk +uses the asynchronous model defined by Microsoft's +[@https://msdn.microsoft.com/en-us/library/dd504870.aspx [*Concurrency Runtime]]. +Identifiers from the [@https://msdn.microsoft.com/en-us/library/jj987780.aspx [*`pplx` namespace]] +define common asynchronous patterns such as tasks and events. The +`concurrency::streams::istream` parameter and `m_data_available` data member +indicates a lack of separation of concerns. The representation of HTTP messages +should not be conflated with the asynchronous model used to serialize or +parse those messages in the message declarations. + +The next declaration forms the complete implementation class referenced by the +handle in the public interface (which follows after): +``` +/// Internal representation of an HTTP request message. +class _http_request final : public http::details::http_msg_base, public std::enable_shared_from_this<_http_request> +{ +public: + _ASYNCRTIMP _http_request(http::method mtd); + + _ASYNCRTIMP _http_request(std::unique_ptr server_context); + + http::method &method() { return m_method; } + + const pplx::cancellation_token &cancellation_token() const { return m_cancellationToken; } + + _ASYNCRTIMP pplx::task reply(const http_response &response); + +private: + + // Actual initiates sending the response, without checking if a response has already been sent. + pplx::task _reply_impl(http_response response); + + http::method m_method; + + std::shared_ptr m_progress_handler; +}; + +} // namespace details +``` + +As before, we note that the implementation class for HTTP requests concerns +itself more with the mechanics of sending the message asynchronously than +it does with actually modeling the HTTP message as described in __rfc7230__: + +* The constructor accepting `std::unique_ptrmethod(); } + + void set_method(const http::method &method) const { _m_impl->method() = method; } + + /// Extract the body of the request message as a string value, checking that the content type is a MIME text type. + /// A body can only be extracted once because in some cases an optimization is made where the data is 'moved' out. + pplx::task extract_string(bool ignore_content_type = false) + { + auto impl = _m_impl; + return pplx::create_task(_m_impl->_get_data_available()).then([impl, ignore_content_type](utility::size64_t) { return impl->extract_string(ignore_content_type); }); + } + + /// Extracts the body of the request message into a json value, checking that the content type is application/json. + /// A body can only be extracted once because in some cases an optimization is made where the data is 'moved' out. + pplx::task extract_json(bool ignore_content_type = false) const + { + auto impl = _m_impl; + return pplx::create_task(_m_impl->_get_data_available()).then([impl, ignore_content_type](utility::size64_t) { return impl->_extract_json(ignore_content_type); }); + } + + /// Sets the body of the message to the contents of a byte vector. If the 'Content-Type' + void set_body(const std::vector &body_data); + + /// Defines a stream that will be relied on to provide the body of the HTTP message when it is + /// sent. + void set_body(const concurrency::streams::istream &stream, const utility::string_t &content_type = _XPLATSTR("application/octet-stream")); + + /// Defines a stream that will be relied on to hold the body of the HTTP response message that + /// results from the request. + void set_response_stream(const concurrency::streams::ostream &stream); + { + return _m_impl->set_response_stream(stream); + } + + /// Defines a callback function that will be invoked for every chunk of data uploaded or downloaded + /// as part of the request. + void set_progress_handler(const progress_handler &handler); + +private: + friend class http::details::_http_request; + friend class http::client::http_client; + + std::shared_ptr _m_impl; +}; +``` + +It is clear from this declaration that the goal of the message model in +this library is driven by its use-case (interacting with REST servers) +and not to model HTTP messages generally. We note problems similar to +the other declarations: + +* There are no compile-time customization points at all. The only + customization is in the `concurrency::streams::istream` and + `concurrency::streams::ostream` reference parameters. Presumably, + these are abstract interfaces which may be subclassed by users + to achieve custom behaviors. + +* The extraction of the body is conflated with the asynchronous model. + +* No way to define an allocator for the container used when extracting + the body. + +* A body can only be extracted once, limiting the use of this container + when using a functional programming style. + +* Setting the body requires either a vector or a `concurrency::streams::istream`. + No user defined types are possible. + +* The HTTP request container conflates HTTP response behavior (see the + `set_response_stream` member). Again this is likely purpose-driven but + the lack of separation of concerns limits this library to only the + uses explicitly envisioned by the authors. + +The general theme of the HTTP message model in cpprestsdk is "no user +definable customizations". There is no allocator support, and no +separation of concerns. It is designed to perform a specific set of +behaviors. In other words, it does not follow the open/closed principle. + +Tasks in the Concurrency Runtime operate in a fashion similar to +`std::future`, but with some improvements such as continuations which +are not yet in the C++ standard. The costs of using a task based +asynchronous interface instead of completion handlers is well +documented: synchronization points along the call chain of composed +task operations which cannot be optimized away. See: +[@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3747.pdf +[*A Universal Model for Asynchronous Operations]] (Kohlhoff). + +[endsect] diff --git a/doc/9_3_websocket_zaphoyd.qbk b/doc/9_3_websocket_zaphoyd.qbk new file mode 100644 index 0000000000..80fbe793b1 --- /dev/null +++ b/doc/9_3_websocket_zaphoyd.qbk @@ -0,0 +1,445 @@ +[/ + 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) +] + +[section Comparison to Zaphoyd Studios WebSocket++] + +[variablelist + +[[ + How does this compare to [@https://www.zaphoyd.com/websocketpp websocketpp], + an alternate header-only WebSocket implementation? +][ +[variablelist + +[[1. Synchronous Interface][ + +Beast offers full support for WebSockets using a synchronous interface. It +uses the same style of interfaces found in Boost.Asio: versions that throw +exceptions, or versions that return the error code in a reference parameter: + +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L774 Beast]] + [websocketpp] + ][ + [``` + template + void + read(DynamicBuffer& dynabuf) + ```] + [ + // + ] +]]]] + +[[2. Connection Model][ + +websocketpp supports multiple transports by utilizing a trait, the `config::transport_type` +([@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/asio/connection.hpp#L60 asio transport example]) +To get an idea of the complexity involved with implementing a transport, +compare the asio transport to the +[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/iostream/connection.hpp#L59 `iostream` transport] +(a layer that allows websocket communication over a `std::iostream`). + +In contrast, Beast abstracts the transport by defining just one [*`NextLayer`] +template argument The type requirements for [*`NextLayer`] are +already familiar to users as they are documented in Asio: +__AsyncReadStream__, __AsyncWriteStream__, __SyncReadStream__, __SyncWriteStream__. + +The type requirements for instantiating `beast::websocket::stream` versus +`websocketpp::connection` with user defined types are vastly reduced +(18 functions versus 2). Note that websocketpp connections are passed by +`shared_ptr`. Beast does not use `shared_ptr` anywhere in its public interface. +A `beast::websocket::stream` is constructible and movable in a manner identical +to a `boost::asio::ip::tcp::socket`. Callers can put such objects in a +`shared_ptr` if they want to, but there is no requirement to do so. + +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp Beast]] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L234 websocketpp]] + ][ + [``` + template + class stream + { + NextLayer next_layer_; + ... + } + ```] + [``` + template + class connection + : public config::transport_type::transport_con_type + , public config::connection_base + { + public: + typedef lib::shared_ptr ptr; + ... + } + ```] +]]]] + +[[3. Client and Server Role][ + +websocketpp provides multi-role support through a hierarchy of +different classes. A `beast::websocket::stream` is role-agnostic, it +offers member functions to perform both client and server handshakes +in the same class. The same types are used for client and server +streams. + +[table + [ + [Beast] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/roles/server_endpoint.hpp#L39 websocketpp], + [@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/roles/client_endpoint.hpp#L42 also]] + ][ + [ + // + ] + [``` + template + class client : public endpoint,config>; + template + class server : public endpoint,config>; + ```] +]]]] + +[[4. Thread Safety][ + +websocketpp uses mutexes to protect shared data from concurrent +access. In contrast, Beast does not use mutexes anywhere in its +implementation. Instead, it follows the Asio pattern. Calls to +asynchronous initiation functions use the same method to invoke +intermediate handlers as the method used to invoke the final handler, +through the __asio_handler_invoke__ mechanism. + +The only requirement in Beast is that calls to asynchronous initiation +functions are made from the same implicit or explicit strand. For +example, if the `io_service` associated with a `beast::websocket::stream` +is single threaded, this counts as an implicit strand and no performance +costs associated with mutexes are incurred. + +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/impl/read_frame_op.ipp#L118 Beast]] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/iostream/connection.hpp#L706 websocketpp]] + ][ + [``` + template + friend + void asio_handler_invoke(Function&& f, read_frame_op* op) + { + return boost_asio_handler_invoke_helpers::invoke(f, op->d_->h); + } + ```] + [``` + mutex_type m_read_mutex; + ```] +]]]] + +[[5. Callback Model][ + +websocketpp requires a one-time call to set the handler for each event +in its interface (for example, upon message receipt). The handler is +represented by a `std::function` equivalent. Its important to recognize +that the websocketpp interface performs type-erasure on this handler. + +In comparison, Beast handlers are specified in a manner identical to +Boost.Asio. They are function objects which can be copied or moved but +most importantly they are not type erased. The compiler can see +through the type directly to the implementation, permitting +optimization. Furthermore, Beast follows the Asio rules for treatment +of handlers. It respects any allocation, continuation, or invocation +customizations associated with the handler through the use of argument +dependent lookup overloads of functions such as `asio_handler_allocate`. + +The Beast completion handler is provided at the call site. For each +call to an asynchronous initiation function, it is guaranteed that +there will be exactly one final call to the handler. This functions +exactly the same way as the asynchronous initiation functions found in +Boost.Asio, allowing the composition of higher level abstractions. + +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L834 Beast]] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L281 websocketpp], + [@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L473 also]] + ][ + [``` + template< + class DynamicBuffer, // Supports user defined types + class ReadHandler // Handler is NOT type-erased + > + typename async_completion< // Return value customization + ReadHandler, // supports futures and coroutines + void(error_code) + >::result_type + async_read( + DynamicBuffer& dynabuf, + ReadHandler&& handler); + ```] + [``` + typedef lib::function< + void(connection_hdl,message_ptr) + > message_handler; + void set_message_handler(message_handler h); + ```] +]]]] + +[[6. Extensible Asynchronous Model][ + +Beast fully supports the +[@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3896.pdf Extensible Asynchronous Model] +developed by Christopher Kohlhoff, author of Boost.Asio (see Section 8). + +Beast websocket asynchronous interfaces may be used seamlessly with +`std::future` stackful/stackless coroutines, or user defined customizations. + +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/impl/stream.ipp#L378 Beast]] + [websocketpp] + ][ + [``` + beast::async_completion< + ReadHandler, + void(error_code)> completion{handler}; + read_op< + DynamicBuffer, decltype(completion.handler)>{ + completion.handler, *this, op, buffer}; + + return completion.result.get(); // Customization point + ```] + [ + // + ] +]]]] + +[[7. Message Buffering][ + +websocketpp defines a message buffer, passed in arguments by +`shared_ptr`, and an associated message manager which permits +aggregation and reuse of memory. The implementation of +`websocketpp::message` uses a `std::string` to hold the payload. If an +incoming message is broken up into multiple frames, the string may be +reallocated for each continuation frame. The `std::string` always uses +the standard allocator, it is not possible to customize the choice of +allocator. + +Beast allows callers to specify the object for receiving the message +or frame data, which is of any type meeting the requirements of +__DynamicBuffer__ (modeled after `boost::asio::streambuf`). + +Beast comes with the class __basic_multi_buffer__, an efficient +implementation of the __DynamicBuffer__ concept which makes use of multiple +allocated octet arrays. If an incoming message is broken up into +multiple pieces, no reallocation occurs. Instead, new allocations are +appended to the sequence when existing allocations are filled. Beast +does not impose any particular memory management model on callers. The +__basic_multi_buffer__ provided by beast supports standard allocators through +a template argument. Use the __DynamicBuffer__ that comes with beast, +customize the allocator if you desire, or provide your own type that +meets the requirements. + +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L774 Beast]] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/message_buffer/message.hpp#L78 websocketpp]] + ][ + [``` + template + read(DynamicBuffer& dynabuf); + ```] + [``` + template class con_msg_manager> + class message { + public: + typedef lib::shared_ptr ptr; + ... + std::string m_payload; + ... + }; + ```] +]]]] + +[[8. Sending Messages][ + +When sending a message, websocketpp requires that the payload is +packaged in a `websocketpp::message` object using `std::string` as the +storage, or it requires a copy of the caller provided buffer by +constructing a new message object. Messages are placed onto an +outgoing queue. An asynchronous write operation runs in the background +to clear the queue. No user facing handler can be registered to be +notified when messages or frames have completed sending. + +Beast doesn't allocate or make copies of buffers when sending data. The +caller's buffers are sent in-place. You can use any object meeting the +requirements of +[@http://www.boost.org/doc/html/boost_asio/reference/ConstBufferSequence.html ConstBufferSequence], +permitting efficient scatter-gather I/O. + +The [*ConstBufferSequence] interface allows callers to send data from +memory-mapped regions (not possible in websocketpp). Callers can also +use the same buffers to send data to multiple streams, for example +broadcasting common subscription data to many clients at once. For +each call to `async_write` the completion handler is called once when +the data finishes sending, in a manner identical to `boost::asio::async_write`. + +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L1048 Beast]] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L672 websocketpp]] + ][ + [``` + template + void + write(ConstBufferSequence const& buffers); + ```] + [``` + lib::error_code send(std::string const & payload, + frame::opcode::value op = frame::opcode::text); + ... + lib::error_code send(message_ptr msg); + ```] +]]]] + +[[9. Streaming Messages][ + +websocketpp requires that the entire message fit into memory, and that +the size is known ahead of time. + +Beast allows callers to compose messages in individual frames. This is +useful when the size of the data is not known ahead of time or if it +is not desired to buffer the entire message in memory at once before +sending it. For example, sending periodic output of a database query +running on a coroutine. Or sending the contents of a file in pieces, +without bringing it all into memory. + +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L1151 Beast]] + [websocketpp] + ][ + [``` + template + void + write_frame(bool fin, + ConstBufferSequence const& buffers); + ```] + [ + // + ] +]]]] + +[[10. Flow Control][ + +The websocketpp read implementation continuously reads asynchronously +from the network and buffers message data. To prevent unbounded growth +and leverage TCP/IP's flow control mechanism, callers can periodically +turn this 'read pump' off and back on. + +In contrast a `beast::websocket::stream` does not independently begin +background activity, nor does it buffer messages. It receives data only +when there is a call to an asynchronous initiation function (for +example `beast::websocket::stream::async_read`) with an associated handler. +Applications do not need to implement explicit logic to regulate the +flow of data. Instead, they follow the traditional model of issuing a +read, receiving a read completion, processing the message, then +issuing a new read and repeating the process. + +[table + [ + [Beast] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L728 websocketpp]] + ][ + [ + // + ] + [``` + lib::error_code pause_reading(); + lib::error_code resume_reading(); + ```] +]]]] + +[[11. Connection Establishment][ + +websocketpp offers the `endpoint` class which can handle binding and +listening to a port, and spawning connection objects. + +Beast does not reinvent the wheel here, callers use the interfaces +already in `boost::asio` for receiving incoming connections resolving +host names, or establishing outgoing connections. After the socket (or +`boost::asio::ssl::stream`) is connected, the `beast::websocket::stream` +is constructed around it and the WebSocket handshake can be performed. + +Beast users are free to implement their own "connection manager", but +there is no requirement to do so. + +[table + [ + [[@http://www.boost.org/doc/html/boost_asio/reference/async_connect.html Beast], + [@http://www.boost.org/doc/html/boost_asio/reference/basic_socket_acceptor/async_accept.html also]] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/asio/endpoint.hpp#L52 websocketpp]] + ][ + [``` + #include + ```] + [``` + template + class endpoint : public config::socket_type; + ```] +]]]] + +[[12. WebSocket Handshaking][ + +Callers invoke `beast::websocket::accept` to perform the WebSocket +handshake, but there is no requirement to use this function. Advanced +users can perform the WebSocket handshake themselves. Beast WebSocket +provides the tools for composing the request or response, and the +Beast HTTP interface provides the container and algorithms for sending +and receiving HTTP/1 messages including the necessary HTTP Upgrade +request for establishing the WebSocket session. + +Beast allows the caller to pass the incoming HTTP Upgrade request for +the cases where the caller has already received an HTTP message. +This flexibility permits novel and robust implementations. For example, +a listening socket that can handshake in multiple protocols on the +same port. + +Sometimes callers want to read some bytes on the socket before reading +the WebSocket HTTP Upgrade request. Beast allows these already-received +bytes to be supplied to an overload of the accepting function to permit +sophisticated features. For example, a listening socket that can +accept both regular WebSocket and Secure WebSocket (SSL) connections. + +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L501 Beast], + [@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L401 also]] + [websocketpp] + ][ + [``` + template + void + accept(ConstBufferSequence const& buffers); + + template + void + accept(http::header> const& req); + ```] + [ + // + ] +]]]] + +] +]] + +] + +[endsect] diff --git a/doc/9_4_faq.qbk b/doc/9_4_faq.qbk new file mode 100644 index 0000000000..99ba6c20af --- /dev/null +++ b/doc/9_4_faq.qbk @@ -0,0 +1,283 @@ +[/ + 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) +] + +[section FAQ] + +To set realistic expectations and prevent a litany of duplicate review +statements, these notes address the most common questions and comments +about Beast and other HTTP libraries that have gone through formal review. + +[variablelist +[[ + "Beast requires too much user code to do anything!" +][ + It is not the intention of the library to provide turn-key + solutions for specific HTTP or WebSocket use-cases. + Instead, it is a sensible protocol layering on top of + Boost.Asio which retains the Boost.Asio memory + management style and asynchronous model. +]] +[[ + "Beast does not offer an HTTP server?" +][ + Beast has a functional HTTP server in the example directory. The + server supports both HTTP and WebSocket using synchronous and + asynchronous shared or dedicated ports. In addition, the server + supports encrypted TLS connections if OpenSSL is available, on + dedicated ports. And the server comes with a "multi-port", a + flexible single port which supports both encrypted and unencrypted + connections, both HTTP and WebSocket, all on the same port. The + server is not part of Beast's public interfaces, as that + functionality is outside the scope of the library. The author + feels that attempting to broaden the scope of the library will + reduce its appeal for standardization. +]] +[[ + "Beast does not offer an HTTP client?" +][ + "I just want to download a resource using HTTP" is a common + cry from users and reviewers. Such functionality is beyond + the scope of Beast. Building a full featured HTTP client is + a difficult task and large enough to deserve its own library. + There are many things to deal with such as the various message + body encodings, complex parsing of headers, difficult header + semantics such as Range and Cache-Control, redirection, + Expect:100-continue, connection retrying, domain name + resolution, TLS, and much, much more. It is the author's + position that Boost first needs a common set of nouns and + verbs for manipulating HTTP at the protocol level; Beast + provides that language. +]] +[[ + "There's no HTTP/2 support yet!" +][ + Many reviewers feel that HTTP/2 support is an essential feature of + a HTTP library. The authors agree that HTTP/2 is important but also + feel that the most sensible implementation is one that does not re-use + the same network reading and writing interface for 2 as that for 1.0 + and 1.1. + + The Beast HTTP message model was designed with the new protocol + in mind and should be evaluated in that context. There are plans + to add HTTP/2 in the future, but there is no rush to do so. + Users can work with HTTP/1 now; we should not deny them that + functionality today to wait for a newer protocol tomorrow. + It is the author's position that there is sufficient value in + Beast's HTTP/1-only implementation that the lack of HTTP/2 + should not be a barrier to acceptance. + + The Beast HTTP message model is suitable for HTTP/2 and can be re-used. + The IETF HTTP Working Group adopted message compatibility with HTTP/1.x + as an explicit goal. A parser can simply emit full headers after + decoding the compressed HTTP/2 headers. The stream ID is not logically + part of the message but rather message metadata and should be + communicated out-of-band (see below). HTTP/2 sessions begin with a + traditional HTTP/1.1 Upgrade similar in fashion to the WebSocket + upgrade. An HTTP/2 implementation can use existing Beast.HTTP primitives + to perform this handshake. +]] +[[ + "This should work with standalone-Asio!" +][ + Beast uses more than Boost.Asio, it depends on various other parts + of Boost. The standalone Asio is currently farther ahead than the + Boost version. Keeping Beast maintained against both versions of + Asio is beyond the resources of the author at the present time. + Compatibility with non-Boost libraries should not be an acceptance + criteria. Beast is currently designed to be a part of Boost: + nothing more, nothing less. Looking at the bigger picture, it + is the author's goal to propose this library for standardization. + A logical track for achieving this is as follows: + + [ordered_list + [ + Boost library acceptance. + ][ + Port to the Boost.Asio version of Networking-TS (This has to wait + until Boost's version of Asio is updated). + ][ + Wait for Networking-TS to become an official part of C++. + ][ + Port to the standard library versions of networking (gcc, clang, msvc). + ][ + Develop proposed language (This can happen concurrently with steps 3 and 4) + ]] +]] +[[ + "You need benchmarks!" +][ + The energy invested in Beast went into the design of the interfaces, + not performance. That said, the most sensitive parts of Beast have + been optimized or designed with optimization in mind. The slow parts + of WebSocket processing have been optimized, and the HTTP parser design + is lifted from another extremely popular project which has performance + as a design goal (see [@https://github.com/h2o/picohttpparser]). + + From: [@http://www.boost.org/development/requirements.html] + + "Aim first for clarity and correctness; optimization should + be only a secondary concern in most Boost libraries." + + As the library matures it will undergo optimization passes; benchmarks + will logically accompany this process. There is a small benchmarking + program included in the tests which compares the performance of + Beast's parser to the NodeJS reference parser, as well as some + benchmarks which compare the performance of various Beast dynamic + buffer implementations against Asio's. +]] +[[ + "Beast is a terrible name!" +][ + The name "Boost.Http" or "Boost.WebSocket" would mislead users into + believing they could perform an HTTP request on a URL or put up a + WebSocket client or server in a couple of lines of code. Where + would the core utilities go? Very likely it would step on the + owner of Boost.Asio's toes to put things in the boost/asio + directory; at the very least, it would create unrequested, + additional work for the foreign repository. + + "Beast" is sufficiently vague as to not suggest any particular + functionality, while acting as a memorable umbrella term for a + family of low level containers and algorithms. People in the know + or with a need for low-level network protocol operations will + have no trouble finding it, and the chances of luring a novice + into a bad experience are greatly reduced. + There is precedent for proper names: "Hana", "Fusion", "Phoenix", + and "Spirit" come to mind. Is "Beast" really any worse than say, + "mp11" for example? + Beast also already has a growing body of users and attention from + the open source community, the name Beast comes up in reddit posts + and StackOverflow as the answer to questions about which HTTP or + WebSocket library to use. +]] + + + +[[ + "Some more advanced examples, e.g. including TLS with client/server + certificates would help." +][ + The server-framework example demonstrates how to implement a server + that supports TLS using certificates. There are also websocket and + HTTP client examples which use TLS. Furthermore, management of + certificates is beyond the scope of the public interfaces of the + library. Asio already provides documentation, interfaces, and + examples for performing these tasks - Beast does not intend to + reinvent them or to redundantly provide this information. +]] + +[[ + "A built-in HTTP router?" +][ + We presume this means a facility to match expressions against the URI + in HTTP requests, and dispatch them to calling code. The authors feel + that this is a responsibility of higher level code. Beast does + not try to offer a web server. That said, the server-framework + example has a concept of request routing called a Service. Two + services are provided, one for serving files and the other for + handling WebSocket upgrade requests. +]] + +[[ + "HTTP Cookies? Forms/File Uploads?" +][ + Cookies, or managing these types of HTTP headers in general, is the + responsibility of higher levels. Beast just tries to get complete + messages to and from the calling code. It deals in the HTTP headers just + enough to process the message body and leaves the rest to callers. However, + for forms and file uploads the symmetric interface of the message class + allows HTTP requests to include arbitrary body types including those needed + to upload a file or fill out a form. +]] + +[[ + "...supporting TLS (is this a feature? If not this would be a show-stopper), + etc." +][ + Beast works with the Stream concept, so it automatically works with the + `boost::asio::ssl::stream` that you have already set up through Asio. +]] + +[[ + "There should also be more examples of how to integrate the http service + with getting files from the file system, generating responses CGI-style" +][ + The design goal for the library is to not try to invent a web server. + We feel that there is a strong need for a basic implementation that + models the HTTP message and provides functions to send and receive them + over Asio. Such an implementation should serve as a building block upon + which higher abstractions such as the aforementioned HTTP service or + cgi-gateway can be built. + + There are several HTTP servers in the example directory which deliver + files, as well as some tested and compiled code snippets which can be + used as a starting point for interfacing with other processes. +]] + +[[ + "You should send a 100-continue to ask for the rest of the body if required." +][ + Deciding on whether to send the "Expect: 100-continue" header or + how to handle it on the server side is the caller's responsibility; + Beast provides the functionality to send or inspect the header before + sending or reading the body. +]] + + + +[[ + "I would also like to see instances of this library being used + in production. That would give some evidence that the design + works in practice." +][ + Beast has already been on public servers receiving traffic and handling + hundreds of millions of dollars' worth of financial transactions daily. + The servers run [*rippled], open source software + ([@https://github.com/ripple/rippled repository]) + implementing the + [@https://ripple.com/files/ripple_consensus_whitepaper.pdf [*Ripple Consensus Protocol]], + technology provided by [@http://ripple.com Ripple]. + + Furthermore, the repository has grown significantly in popularity in + 2017. There are many users, and some of them participate directly in + the repository by reporting issues, performing testing, and in some + cases submitting pull requests with code contributions. +]] + + + +[[ + What about WebSocket message compression? +][ + Beast WebSocket supports the permessage-deflate extension described in + [@https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-00 draft-ietf-hybi-permessage-compression-00]. + The library comes with a header-only, C++11 port of ZLib's "deflate" codec + used in the implementation of the permessage-deflate extension. +]] +[[ + Where is the WebSocket TLS/SSL interface? +][ + The `websocket::stream` wraps the socket or stream that you provide + (for example, a `boost::asio::ip::tcp::socket` or a + `boost::asio::ssl::stream`). You establish your TLS connection using the + interface on `ssl::stream` like shown in all of the Asio examples, then + construct your `websocket::stream` around it. It works perfectly fine; + Beast comes with an `ssl_stream` wrapper in the example directory which + allows the SSL stream to be moved, overcoming an Asio limitation. + + The WebSocket implementation [*does] provide support for shutting down + the TLS connection through the use of the ADL compile-time virtual functions + [link beast.ref.beast__websocket__teardown `teardown`] and + [link beast.ref.beast__websocket__async_teardown `async_teardown`]. These will + properly close the connection as per rfc6455 and overloads are available + for TLS streams. Callers may provide their own overloads of these functions + for user-defined next layer types. +]] + +] + +[endsect] diff --git a/doc/Jamfile.v2 b/doc/Jamfile similarity index 77% rename from doc/Jamfile.v2 rename to doc/Jamfile index 39e9ecfbbb..24b2cf7e40 100644 --- a/doc/Jamfile.v2 +++ b/doc/Jamfile @@ -17,13 +17,13 @@ using doxygen ; import quickbook ; -path-constant here : . ; +path-constant out : . ; install stylesheets : $(broot)/doc/src/boostbook.css : - $(here)/html + $(out)/html ; explicit stylesheets ; @@ -32,10 +32,9 @@ install images : [ glob $(broot)/doc/src/images/*.png ] images/beast.png - images/body.png images/message.png : - $(here)/html/images + $(out)/html/images ; explicit images ; @@ -44,28 +43,14 @@ install callouts : [ glob $(broot)/doc/src/images/callouts/*.png ] : - $(here)/html/images/callouts + $(out)/html/images/callouts ; explicit callout ; -install examples - : - [ glob - ../examples/*.cpp - ../examples/*.hpp - ../examples/ssl/*.cpp - ../examples/ssl/*.hpp - ] - : - $(here)/html/examples - ; - -explicit examples ; - xml doc : - master.qbk + 0_main.qbk : temp $(broot)/tools/boostbook/dtd @@ -86,11 +71,10 @@ boostbook boostdoc toc.section.depth=8 # How deep should recursive sections appear in the TOC? toc.max.depth=8 # How many levels should be created for each TOC? generate.section.toc.level=8 # Control depth of TOC generation in sections - generate.toc="chapter nop section nop" + generate.toc="chapter toc,title section nop reference nop" $(broot)/tools/boostbook/dtd : temp - examples images stylesheets ; diff --git a/doc/README.md b/doc/README.md deleted file mode 100644 index 02a9c555eb..0000000000 --- a/doc/README.md +++ /dev/null @@ -1,72 +0,0 @@ -# Building documentation - -## Specifying Files - -To specify the source files for which to build documentation, modify `INPUT` -and its related fields in `doc/source.dox`. Note that the `INPUT` paths are -relative to the `doc/` directory. - -## Install Dependencies - -### Windows - -Install these dependencies: - -1. Install [Doxygen](http://www.stack.nl/~dimitri/doxygen/download.html) -2. Download the following zip files from [xsltproc](https://www.zlatkovic.com/pub/libxml/) - (Alternate download: ftp://ftp.zlatkovic.com/libxml/), - and extract the `bin\` folder contents into any folder in your path. - * iconv - * libxml2 - * libxslt - * zlib -3. Download [Boost](http://www.boost.org/users/download/) - 1. Extract the compressed file contents to your (new) `$BOOST_ROOT` location. - 2. Open a command prompt or shell in the `$BOOST_ROOT`. - 3. `./bootstrap.bat` - 4. If it is not already there, add your `$BOOST_ROOT` to your environment `$PATH`. - -### MacOS - -1. Install doxygen: - * Use homebrew to install: `brew install doxygen`. The executable will be - installed in `/usr/local/bin` which is already in your path. - * Alternatively, install from here: [doxygen](http://www.stack.nl/~dimitri/doxygen/download.html). - You'll then need to make doxygen available to your command line. You can - do this by adding a symbolic link from `/usr/local/bin` to the doxygen - executable. For example, `$ ln -s /Applications/Doxygen.app/Contents/Resources/doxygen /usr/local/bin/doxygen` -2. Install [Boost](http://www.boost.org/users/download/) - 1. Extract the compressed file contents to your (new) `$BOOST_ROOT` location. - 2. Open a command prompt or shell in the `$BOOST_ROOT`. - 3. `$ ./bootstrap.bat` - 4. If it is not already there, add your `$BOOST_ROOT` to your environment - `$PATH`. This makes the `b2` command available to the command line. -3. That should be all that's required. In OS X 10.11, at least, libxml2 and - libxslt come pre-installed. - -### Linux - -1. Install [Docker](https://docs.docker.com/engine/installation/) -2. Build Docker image. From the Beast root folder: -``` -sudo docker build -t beast-docs doc/ -``` - -## Do it - -### Windows & MacOS - -From the Beast root folder: -``` -cd doc -./makeqbk.sh && b2 -``` -The output will be in `doc/html`. - -### Linux - -From the Beast root folder: -``` -sudo docker run -v $PWD:/opt/beast --rm beast-docs -``` -The output will be in `doc/html`. diff --git a/doc/concept/Body.qbk b/doc/concept/Body.qbk new file mode 100644 index 0000000000..5421286afe --- /dev/null +++ b/doc/concept/Body.qbk @@ -0,0 +1,105 @@ +[/ + 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) +] + +[section:Body Body] + +A [*Body] type is supplied as a template argument to the __message__ class. It +controls both the type of the data member of the resulting message object, and +the algorithms used during parsing and serialization. + +In this table: + +* `X` is a type meeting the requirements of [*Body]. + +* `m` is a value of type `message` where `b` is a `bool` value + and `F` is a type meeting the requirements of [*Fields]. + +[table Body requirements +[[expression] [type] [semantics, pre/post-conditions]] +[ + [`X::value_type`] + [] + [ + The type of the `message::body` member. + If this is not movable or not copyable, the containing message + will be not movable or not copyable. + ] +][ + [`X::writer`] + [] + [ + If present, indicates that the body can hold a message body + parsing result. The type must meet the requirements of + __BodyWriter__. The implementation constructs an object of + this type to obtain buffers into which parsed body octets + are placed. + ] +][ + [`X::reader`] + [] + [ + If present, indicates that the body is serializable. The type + must meet the requirements of __BodyReader__. The implementation + constructs an object of this type to obtain buffers representing + the message body for serialization. + ] +][ + [`X::size(X::value_type v)`] + [`std::uint64_t`] + [ + This static member function is optional. It returns the payload + size of `v` not including any chunked transfer encoding. The + function shall not exit via an exception. + + When this function is present: + + * The function shall not fail + + * A call to + [link beast.ref.beast__http__message.payload_size `message::payload_size`] + will return the same value as `size`. + + * A call to + [link beast.ref.beast__http__message.prepare_payload `message::prepare_payload`] + will remove "chunked" from the Transfer-Encoding field if it appears + as the last encoding, and will set the Content-Length field to the + returned value. + + Otherwise, when the function is omitted: + + * A call to + [link beast.ref.beast__http__message.payload_size `message::payload_size`] + will return `boost::none`. + + * A call to + [link beast.ref.beast__http__message.prepare_payload `message::prepare_payload`] + will erase the Content-Length field, and add "chunked" as the last + encoding in the Transfer-Encoding field if it is not already present. + ] +][ + [`is_body`] + [`std::true_type`] + [ + An alias for `std::true_type` for `X`, otherwise an alias + for `std::false_type`. + ] +] +] + +[heading Exemplar] + +[concept_Body] + +[heading Models] + +* [link beast.ref.beast__http__basic_dynamic_body `basic_dynamic_body`] +* [link beast.ref.beast__http__buffer_body `buffer_body`] +* [link beast.ref.beast__http__dynamic_body `dynamic_body`] +* [link beast.ref.beast__http__empty_body `empty_body`] +* [link beast.ref.beast__http__string_body `string_body`] + +[endsect] diff --git a/doc/concept/BodyReader.qbk b/doc/concept/BodyReader.qbk new file mode 100644 index 0000000000..ec13aa1632 --- /dev/null +++ b/doc/concept/BodyReader.qbk @@ -0,0 +1,117 @@ +[/ + 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) +] + +[section:BodyReader BodyReader] + +A [*BodyReader] provides an online algorithm to obtain a sequence of zero +or more buffers from a body during serialization. The implementation creates +an instance of this type when needed, and calls into it one or more times to +retrieve buffers holding body octets. The interface of [*BodyReader] is +intended to obtain buffers for these scenarios: + +* A body that does not entirely fit in memory. +* A body produced incrementally from coroutine output. +* A body represented by zero or more buffers already in memory. +* A body whose size is not known ahead of time. +* Body data generated dynamically from other threads. +* Body data computed algorithmically. + +In this table: + +* `X` denotes a type meeting the requirements of [*BodyReader]. + +* `B` denotes a __Body__ where + `std::is_same::value == true`. + +* `a` denotes a value of type `X`. + +* `m` denotes a possibly const value of type `message&` where + `std::is_same:value == true`. + +* `ec` is a value of type [link beast.ref.beast__error_code `error_code&`]. + +* `R` is the type `boost::optional>`. + +[heading Associated Types] + +* __Body__ + +* [link beast.ref.beast__http__is_body_reader `is_body_reader`] + +[heading BodyReader requirements] +[table Valid Expressions +[[Expression] [Type] [Semantics, Pre/Post-conditions]] +[ + [`X::const_buffers_type`] + [] + [ + A type which meets the requirements of __ConstBufferSequence__. + This is the type of buffer returned by `X::get`. + ] +][ + [`X(m);`] + [] + [ + Constructible from `m`. The lifetime of `m` is guaranteed + to end no earlier than after the `X` is destroyed. + The reader shall not access the contents of `m` before the + first call to `init`, permitting lazy construction of the + message. + + The constructor may optionally require that `m` is const, which + has these consequences: + + * If `X` requires that `m` is a const reference, then serializers + constructed for messages with this body type will also require a + const reference to a message, otherwise: + + * If `X` requires that `m` is a non-const reference, then serializers + constructed for messages with this body type will aso require + a non-const reference to a message. + ] +][ + [`a.init(ec)`] + [] + [ + Called once to fully initialize the object before any calls to + `get`. The message body becomes valid before entering this function, + and remains valid until the reader is destroyed. + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if there was one. + ] +][ + [`a.get(ec)`] + [`R`] + [ + Called one or more times after `init` succeeds. This function + returns `boost::none` if all buffers representing the body have + been returned in previous calls or if it sets `ec` to indicate an + error. Otherwise, if there are buffers remaining the function + should return a pair with the first element containing a non-zero + length buffer sequence representing the next set of octets in + the body, while the second element is a `bool` meaning `true` + if there may be additional buffers returned on a subsequent call, + or `false` if the buffer returned on this call is the last + buffer representing the body. + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if there was one. + ] +] +] + +[heading Exemplar] + +[concept_BodyReader] + +[heading Models] + +* [link beast.ref.beast__http__basic_dynamic_body.reader `basic_dynamic_body::reader`] +* [link beast.ref.beast__http__basic_file_body__reader `basic_file_body::reader`] +* [link beast.ref.beast__http__basic_string_body.reader `basic_string_body::reader`] +* [link beast.ref.beast__http__empty_body.reader `empty_body::reader`] + +[endsect] diff --git a/doc/concept/BodyWriter.qbk b/doc/concept/BodyWriter.qbk new file mode 100644 index 0000000000..54c93fdcba --- /dev/null +++ b/doc/concept/BodyWriter.qbk @@ -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) +] + +[section:BodyWriter BodyWriter] + +A [*BodyWriter] provides an online algorithm to transfer a series of zero +or more buffers containing parsed body octets into a message container. The +__parser__ creates an instance of this type when needed, and calls into +it zero or more times to transfer buffers. The interface of [*BodyWriter] +is intended to allow the conversion of buffers into these scenarios for +representation: + +* Storing a body in a dynamic buffer +* Storing a body in a user defined container with a custom allocator +* Transformation of incoming body data before storage, for example + to compress it first. +* Saving body data to a file + +In this table: + +* `X` denotes a type meeting the requirements of [*BodyWriter]. + +* `B` denotes a __Body__ where + `std::is_same::value == true`. + +* `a` denotes a value of type `X`. + +* `b` is an object whose type meets the requirements of __ConstBufferSequence__ + +* `m` denotes a value of type `message&` where + `std::is_same::value == true`. + +* `n` is a value of type `boost::optional`. + +* `ec` is a value of type [link beast.ref.beast__error_code `error_code&`]. + +[heading Associated Types] + +* __Body__ +* [link beast.ref.beast__http__is_body_writer `is_body_writer`] + +[table Writer requirements +[[expression] [type] [semantics, pre/post-conditions]] +[ + [`X(m);`] + [] + [ + Constructible from `m`. The lifetime of `m` is guaranteed to + end no earlier than after the `X` is destroyed. The constructor + will be called after a complete header is stored in `m`, and + before parsing body octets for messages indicating that a body + is present The writer shall not access the contents of `m` before + the first call to `init`, permitting lazy construction of the + message. + + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if there was one. + ] +][ + [`a.init(n, ec)`] + [] + [ + Called once to fully initialize the object before any calls to + `put`. The message body is valid before entering this function, + and remains valid until the writer is destroyed. + The value of `n` will be set to the content length of the + body if known, otherwise `n` will be equal to `boost::none`. + Implementations of [*BodyWriter] may use this information to + optimize allocation. + + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if there was one. + ] +][ + [`a.put(b,ec)`] + [`std::size_t`] + [ + This function is called to append some or all of the buffers + specified by `b` into the body representation. The number of + bytes inserted from `b` is returned. If the number of bytes + inserted is less than the total input, the remainder of the + input will be presented in the next call to `put`. + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if there was one. + ] +][ + [`a.finish(ec)`] + [] + [ + This function is called when no more body octets are remaining. + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if there was one. + ] +][ + [`is_body_writer`] + [`std::true_type`] + [ + An alias for `std::true_type` for `B`, otherwise an alias + for `std::false_type`. + ] +] +] + +[heading Exemplar] + +[concept_BodyWriter] + +[heading Models] + +* [link beast.ref.beast__http__basic_dynamic_body.writer `basic_dynamic_body::writer`] +* [link beast.ref.beast__http__basic_file_body__reader `basic_file_body::writer`] +* [link beast.ref.beast__http__basic_string_body.writer `basic_string_body::writer`] +* [link beast.ref.beast__http__empty_body.writer `empty_body::writer`] + +[endsect] diff --git a/doc/concept/BufferSequence.qbk b/doc/concept/BufferSequence.qbk new file mode 100644 index 0000000000..812d92ffb5 --- /dev/null +++ b/doc/concept/BufferSequence.qbk @@ -0,0 +1,15 @@ +[/ + 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) +] + +[section:BufferSequence BufferSequence] + +A [*BufferSequence] is a type meeting either of the following requirements: + +* __ConstBufferSequence__ +* __MutableBufferSequence__ + +[endsect] diff --git a/doc/types/DynamicBuffer.qbk b/doc/concept/DynamicBuffer.qbk similarity index 81% rename from doc/types/DynamicBuffer.qbk rename to doc/concept/DynamicBuffer.qbk index 792c25914d..c9abd7df36 100644 --- a/doc/types/DynamicBuffer.qbk +++ b/doc/concept/DynamicBuffer.qbk @@ -5,7 +5,7 @@ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] -[section:DynamicBuffer DynamicBuffer requirements] +[section:DynamicBuffer DynamicBuffer] A dynamic buffer encapsulates memory storage that may be automatically resized as required, where the memory is divided into an input sequence followed by an @@ -19,7 +19,8 @@ The interface to this concept is intended to permit the following implementation strategies: * A single contiguous octet array, which is reallocated as necessary to - accommodate changes in the size of the octet sequence. + accommodate changes in the size of the octet sequence. This is the + implementation approach currently offered by __flat_buffer__. * A sequence of one or more octet arrays, where each array is of the same size. Additional octet array objects are appended to the sequence to @@ -28,19 +29,19 @@ implementation strategies: * A sequence of one or more octet arrays of varying sizes. Additional octet array objects are appended to the sequence to accommodate changes in the size of the character sequence. This is the implementation approach - currently offered by [link beast.ref.basic_streambuf `basic_streambuf`]. + currently offered by __multi_buffer__. -In the table below: +In this table: * `X` denotes a dynamic buffer class. * `a` denotes a value of type `X`. * `c` denotes a (possibly const) value of type `X`. * `n` denotes a value of type `std::size_t`. -* `T` denotes a type meeting the requirements for [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/ConstBufferSequence.html `ConstBufferSequence`]. -* `U` denotes a type meeting the requirements for [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/MutableBufferSequence.html `MutableBufferSequence`]. +* `T` denotes a type meeting the requirements for __ConstBufferSequence__. +* `U` denotes a type meeting the requirements for __MutableBufferSequence__. [table DynamicBuffer requirements -[[operation] [type] [semantics, pre/post-conditions]] +[[expression] [type] [semantics, pre/post-conditions]] [ [`X::const_buffers_type`] [`T`] @@ -122,4 +123,14 @@ In the table below: ] ] +[heading Models] + +* [link beast.ref.beast__basic_flat_buffer `basic_flat_buffer`] +* [link beast.ref.beast__basic_multi_buffer `basic_multi_buffer`] +* [link beast.ref.beast__drain_buffer `drain_buffer`] +* [link beast.ref.beast__flat_buffer `flat_buffer`] +* [link beast.ref.beast__multi_buffer `multi_buffer`] +* [link beast.ref.beast__static_buffer `static_buffer`] +* [link beast.ref.beast__static_buffer_n `static_buffer_n`] + [endsect] diff --git a/doc/concept/Fields.qbk b/doc/concept/Fields.qbk new file mode 100644 index 0000000000..a66a4282c0 --- /dev/null +++ b/doc/concept/Fields.qbk @@ -0,0 +1,225 @@ +[/ + 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) +] + +[section:Fields Fields] + +An instance of [*Fields] is a container for holding HTTP header fields +and their values. The implementation also calls upon the container to +store the request target and non-standard strings for method and obsolete +reason phrase as needed. Types which meet these requirements can always +be serialized. + +[heading Associated Types] + +* __FieldsReader__ + +* [link beast.ref.beast__http__is_fields `is_fields`] + +[heading Requirements] + +In this table: + +* `F` denotes a type that meets the requirements of [*Fields]. + +* `R` denotes a type meeting the requirements of __FieldsReader__. + +* `a` denotes a value of type `F`. + +* `c` denotes a (possibly const) value of type `F`. + +* `b` is a value of type `bool` + +* `n` is a value of type `boost::optional`. + +* `s` is a value of type [link beast.ref.beast__string_view `string_view`]. + +* `v` is a value of type `unsigned int` representing the HTTP-version. + +[table Valid expressions +[[Expression] [Type] [Semantics, Pre/Post-conditions]] +[ + [`F::reader`] + [`R`] + [ + A type which meets the requirements of __FieldsReader__. + ] +][ + [`c.get_method_impl()`] + [`string_view`] + [ + Returns the method text. + The implementation only calls this function for request + headers when retrieving the method text previously set + with a call to `set_method_impl` using a non-empty string. + ] +][ + [`c.get_target_impl()`] + [`string_view`] + [ + Returns the target string. + The implementation only calls this function for request headers. + ] +][ + [`c.get_reason_impl()`] + [`string_view`] + [ + Returns the obsolete request text. + The implementation only calls this for response headers when + retrieving the reason text previously set with a call to + `set_reason_impl` using a non-empty string. + ] +][ + [`c.get_chunked_impl()`] + [`bool`] + [ + Returns `true` if the + [@https://tools.ietf.org/html/rfc7230#section-3.3.1 [*Transfer-Encoding]] + field value indicates that the payload is chunk encoded. Both + of these conditions must be true: + [itemized_list + [ + The Transfer-Encoding field is present in the message. + ][ + The last item the value of the field is "chunked". + ]] + ] +][ + [`c.get_keep_alive_impl(v)`] + [`bool`] + [ + Returns `true` if the semantics of the + [@https://tools.ietf.org/html/rfc7230#section-6.1 [*Connection]] + field and version indicate that the connection should remain + open after the corresponding response is transmitted or received: + + [itemized_list + [ + If `(v < 11)` the function returns `true` if the "keep-alive" + token is present in the Connection field value. Otherwise the + function returns `false`. + ][ + If `(v == 11)`, the function returns `false` if the "close" + token is present in the Connection field value. Otherwise the + function returns `true`. + ]] + ] +][ + [`a.set_method_impl(s)`] + [] + [ + Stores a copy of `s` as the method text, or erases the previously + stored value if `s` is empty. + The implementation only calls this function for request headers. + This function may throw `std::invalid_argument` if the operation + is not supported by the container. + ] +][ + [`a.set_target_impl(s)`] + [] + [ + Stores a copy of `s` as the target, or erases the previously + stored value if `s` is empty. + The implementation only calls this function for request headers. + This function may throw `std::invalid_argument` if the operation + is not supported by the container. + ] +][ + [`a.set_reason_impl(s)`] + [] + [ + Stores a copy of `s` as the reason text, or erases the previously + stored value of the reason text if `s` is empty. + The implementation only calls this function for request headers. + This function may throw `std::invalid_argument` if the operation + is not supported by the container. + ] +][ + [`a.set_chunked_impl(b)`] + [] + [ + Adjusts the + [@https://tools.ietf.org/html/rfc7230#section-3.3.1 [*Transfer-Encoding]] + field as follows: + + [itemized_list + [ + If `b` is `true`, the "chunked" token is appended + to the list of encodings if it does not already appear + last in the list. + If the Transfer-Encoding field is absent, the field will + be inserted to the container with the value "chunked". + ][ + If `b` is `false, the "chunked" token is removed from the + list of encodings if it appears last in the list. + If the result of the removal leaves the list of encodings + empty, the Transfer-Encoding field shall not appear when + the associated __FieldsReader__ serializes the fields. + ]] + ] + +][ + [`a.set_content_length_impl(n)`] + [] + [ + Adjusts the + [@https://tools.ietf.org/html/rfc7230#section-3.3.2 [*Content-Length]] + field as follows: + + [itemized_list + [ + If `n` contains a value, the Content-Length field + will be set to the text representation of the value. + Any previous Content-Length fields are removed from + the container. + ][ + If `n` does not contain a value, any present Content-Length + fields are removed from the container. + ]] + ] + +][ + [`a.set_keep_alive_impl(v,b)`] + [] + [ + Adjusts the + [@https://tools.ietf.org/html/rfc7230#section-6.1 [*Connection]] + field value depending on the values of `v` and `b`. The field + value is treated as + [@https://tools.ietf.org/html/rfc7230#section-6.1 ['connection-option]] + (rfc7230). + + [itemized_list + [ + If `(v < 11 && b)`, then all "close" tokens present in the + value are removed, and the "keep-alive" token is added to + the valueif it is not already present. + ][ + If `(v < 11 && ! b)`, then all "close" and "keep-alive" + tokens present in the value are removed. + + ][ + If `(v == 11 && b)`, then all "keep-alive" and "close" + tokens present in the value are removed. + ][ + If `(v == 11 && ! b)`, then all "keep-alive" tokens present + in the value are removed, and the "close" token is added to + the value if it is not already present. + ]] + ] + +]] + +[heading Exemplar] + +[concept_Fields] + +[heading Models] + +* [link beast.ref.beast__http__basic_fields `basic_fields`] +* [link beast.ref.beast__http__fields `fields`] + +[endsect] diff --git a/doc/concept/FieldsReader.qbk b/doc/concept/FieldsReader.qbk new file mode 100644 index 0000000000..f7fa5fd45f --- /dev/null +++ b/doc/concept/FieldsReader.qbk @@ -0,0 +1,84 @@ +[/ + 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) +] + +[section:FieldsReader FieldsReader] + +A [*FieldsReader] provides a algorithm to obtain a sequence of buffers +representing the complete serialized HTTP/1 header for a set of fields. +The implementation constructs an instance of this type when needed, and +calls into it once to retrieve the buffers. + +[heading Associated Types] + +* __FieldsReader__ + +[heading Requirements] + +In this table: + +* `X` denotes a type that meets the requirements of [*FieldsReader]. + +* `F` denotes a __Fields__ where + `std::is_same::value == true`. + +* `a` is a value of type `X`. + +* `f` is a value of type `F`. + +* `v` is an `unsigned` value representing the HTTP version. + +* `c` is an `unsigned` representing the HTTP status-code. + +* `m` is a value of type [link beast.ref.beast__http__verb `verb`]. + +[table Valid expressions +[[expression][type][semantics, pre/post-conditions]] +[ + [`X::const_buffers_type`] + [] + [ + A type which meets the requirements of __ConstBufferSequence__. + This is the type of buffer returned by `X::get`. + ] +][ + [`X(f,v,m)`] + [] + [ + The implementation calls this constructor to indicate + that the fields being serialized form part of an HTTP + request. The lifetime of `f` is guaranteed + to end no earlier than after the `X` is destroyed. + ] +][ + [`X(f,v,c)`] + [] + [ + The implementation calls this constructor to indicate + that the fields being serialized form part of an HTTP + response. The lifetime of `f` is guaranteed + to end no earlier than after the `X` is destroyed. + ] +][ + [`a.get()`] + [`X::const_buffers_type`] + [ + Called once after construction, this function returns + a constant buffer sequence containing the serialized + representation of the HTTP request or response including + the final carriage return linefeed sequence (`"\r\n"`). + ] +]] + +[heading Exemplar] + +[concept_FieldsReader] + +[heading Models] + +* [link beast.ref.beast__http__basic_fields.reader `basic_fields::reader`] + +[endsect] diff --git a/doc/concept/File.qbk b/doc/concept/File.qbk new file mode 100644 index 0000000000..35decc654d --- /dev/null +++ b/doc/concept/File.qbk @@ -0,0 +1,173 @@ +[/ + Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:File File] + +The [*File] concept abstracts access to files in the underlying file system. +To support other platform interfaces, users may author their own [*File] +types which meet these requirements. + +In this table: + +* `F` is a [*File] type +* `f` is an instance of `F` +* `p` is a value of type `char const*` which points to a null + terminated utf-8 encoded string. +* `m` is an instance of [link beast.ref.beast__file_mode `file_mode`] +* `n` is a number of bytes, convertible to `std::size_t` +* `o` is a byte offset in the file, convertible to `std::uint64_t` +* `b` is any non-const pointer to memory +* `c` is any possibly-const pointer to memory +* `ec` is a reference of type [link beast.ref.beast__error_code `error_code`] + +[heading Associated Types] + +* [link beast.ref.beast__file_mode `file_mode`] +* [link beast.ref.beast__is_file `is_file`] + +[heading File Requirements] +[table Valid Expressions +[[Operation] [Return Type] [Semantics, Pre/Post-conditions]] +[ + [`F()`] + [ ] + [ + Default constructable + ] +] +[ + [`f.~F()`] + [ ] + [ + Destructible. + If `f` refers to an open file, it is first closed + as if by a call to `close` with the error ignored. + ] +] +[ + [`f.is_open()`] + [`bool`] + [ + Returns `true` if `f` refers to an open file, `false` otherwise. + ] +] +[ + [`f.close(ec)`] + [] + [ + If `f` refers to an open file, thie function attempts to + close the file. + Regardless of whether an error occurs or not, a subsequent + call to `f.is_open()` will return `false`. + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if an error + occurred. + ] +] +[ + [`f.open(p,m,ec)`] + [] + [ + Attempts to open the file at the path specified by `p` + with the mode specified by `m`. + Upon success, a subsequent call to `f.is_open()` will + return `true`. + If `f` refers to an open file, it is first closed + as if by a call to `close` with the error ignored. + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if an error + occurred. + + ] +] +[ + [`f.size(ec)`] + [`std::uint64_t`] + [ + If `f` refers to an open file, this function attempts to + determine the file size and return its value. + If `f` does not refer to an open file, the function will + set `ec` to `errc::invalid_argument` and return 0. + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if an error + occurred. + ] +] +[ + [`f.pos(ec)`] + [`std::uint64_t`] + [ + If `f` refers to an open file, this function attempts to + determine the current file offset and return it. + If `f` does not refer to an open file, the function will + set `ec` to `errc::invalid_argument` and return 0. + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if an error + occurred. + ] +] +[ + [`f.seek(o,ec)`] + [] + [ + Attempts to reposition the current file offset to the value + `o`, which represents a byte offset relative to the beginning + of the file. + If `f` does not refer to an open file, the function will + set `ec` to `errc::invalid_argument` and return immediately. + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if an error + occurred. + ] +] +[ + [`f.read(b,n,ec)`] + [`std::size_t`] + [ + Attempts to read `n` bytes starting at the current file offset + from the open file referred to by `f`. + Bytes read are stored in the memory buffer at address `b` which + must be at least `n` bytes in size. + The function advances the file offset by the amount read, and + returns the number of bytes actually read, which may be less + than `n`. + If `f` does not refer to an open file, the function will + set `ec` to `errc::invalid_argument` and return immediately. + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if an error + occurred. + ] +] +[ + [`f.write(c,n,ec)`] + [`std::size_t`] + [ + Attempts to write `n` bytes from the buffer pointed to by `c` to + the current file offset of the open file referred to by `f`. + The memory buffer at `c` must point to storage of at least `n` + bytes meant to be copied to the file. + The function advances the file offset by the amount written, + and returns the number of bytes actually written, which may be + less than `n`. + If `f` does not refer to an open file, the function will + set `ec` to `errc::invalid_argument` and return immediately. + The function will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if an error + occurred. + ] +] +] + +[heading Exemplar] + +[concept_File] + +[heading Models] + +* [link beast.ref.beast__file_stdio `file_stdio`] + +[endsect] diff --git a/doc/concept/Streams.qbk b/doc/concept/Streams.qbk new file mode 100644 index 0000000000..d55322daa6 --- /dev/null +++ b/doc/concept/Streams.qbk @@ -0,0 +1,34 @@ +[/ + 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) +] + +[section:streams Stream] + +Stream types represent objects capable of performing synchronous or +asynchronous I/O. They are based on concepts from `boost::asio`. + +[heading:Stream Stream] + +A type modeling [*Stream] meets either or both of the following requirements: + +* [*AsyncStream] +* [*SyncStream] + +[heading:AsyncStream AsyncStream] + +A type modeling [*AsyncStream] meets the following requirements: + +* __AsyncReadStream__ +* __AsyncWriteStream__ + +[heading:SyncStream SyncStream] + +A type modeling [*SyncStream] meets the following requirements: + +* __SyncReadStream__ +* __SyncWriteStream__ + +[endsect] diff --git a/doc/design.qbk b/doc/design.qbk deleted file mode 100644 index ff925b4ab2..0000000000 --- a/doc/design.qbk +++ /dev/null @@ -1,654 +0,0 @@ -[/ - 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) -] - -[section:design Design Choices] - -[block ''' - - HTTP FAQ - WebSocket FAQ - Comparison to Zaphoyd Studios WebSocket++ - -'''] - -The implementations are driven by business needs of cryptocurrency server -applications (e.g. [@https://ripple.com Ripple]) written in C++. These -needs were not met by existing solutions so Beast was written from scratch -as a solution. Beast's design philosophy avoids flaws exhibited by other -libraries: - -* Don't try to do too much. - -* Don't sacrifice performance. - -* Mimic Boost.Asio; familiarity breeds confidence. - -* Role-symmetric interfaces; client and server the same (or close to it). - -* Leave important decisions to the user, such as allocating memory or - managing flow control. - -Beast uses the __DynamicBuffer__ concept presented in the Networking TS -(__N4588__), and relies heavily on the Boost.Asio __ConstBufferSequence__ -and __MutableBufferSequence__ concepts for passing buffers to functions. -The authors have found the dynamic buffer and buffer sequence interfaces to -be optimal for interacting with Asio, and for other tasks such as incremental -parsing of data in buffers (for example, parsing websocket frames stored -in a [link beast.ref.static_streambuf `static_streambuf`]). - -During the development of Beast the authors have studied other software -packages and in particular the comments left during the Boost Review process -of other packages offering similar functionality. In this section and the -FAQs that follow we attempt to answer those questions that are also applicable -to Beast. - -[variablelist -[[ - "I would also like to see instances of this library being used - in production. That would give some evidence that the design - works in practice." -][ - Beast.HTTP and Beast.WebSocket are production ready and currently - running on public servers receiving traffic and handling millions of - dollars worth of financial transactions daily. The servers run [*rippled], - open source software ([@https://github.com/ripple/rippled repository]) - implementing the - [@https://ripple.com/files/ripple_consensus_whitepaper.pdf [*Ripple Consensus Protocol]], - technology provided by [@http://ripple.com Ripple]. -]] - -] - - - -[section:http HTTP FAQ] - -For HTTP we model the message to maximize flexibility of implementation -strategies while allowing familiar verbs such as [*`read`] and [*`write`]. -The HTTP interface is further driven by the needs of the WebSocket module, -as a WebSocket session requires a HTTP Upgrade handshake exchange at the -start. Other design goals: - -* Keep it simple. - -* Stay low level; don't invent a whole web server or client. - -* Allow for customizations, if the user needs it. - -[variablelist - -[[ - "Some more advanced examples, e.g. including TLS with client/server - certificates would help." -][ - The HTTP interface doesn't try to reinvent the wheel, it just uses - the `boost::asio::ip::tcp::socket` or `boost::asio::ssl::stream` that - you set up beforehand. Callers use the interfaces already existing - on those objects to make outgoing connections, accept incoming connections, - or establish TLS sessions with certificates. We find the available Asio - examples for performing these tasks sufficient. -]] - -[[ - "A built-in router?" -][ - We presume this means a facility to match expressions against the URI - in HTTP requests, and dispatch them to calling code. The authors feel - that this is a responsibility of higher level code. Beast.HTTP does - not try to offer a web server. -]] - -[[ - "Cookies? Forms/File Uploads?" -][ - Cookies, or managing these types of HTTP headers in general, is the - responsibility of higher levels. Beast.HTTP just tries to get complete - messages to and from the calling code. It deals in the HTTP headers just - enough to process the message body and leaves the rest to callers. However, - for forms and file uploads the symmetric interface of the message class - allows HTTP requests to include arbitrary body types including those needed - to upload a file or fill out a form. -]] - -[[ - "...supporting TLS (is this a feature? If not this would be a show-stopper), - etc." -][ - Beast.HTTP does not provide direct facilities for implementing TLS - connections; however, the interfaces already existing on the - `boost::asio::ssl::stream` are available and can be used to establish - secure connections. Then, functions like `http::read` or `http::async_write` - can work with those encrypted connections with no problem. -]] - -[[ - "There should also be more examples of how to integrate the http service - with getting files from the file system, generating responses CGI-style" -][ - The design goal for the library is to not try to invent a web server. - We feel that there is a strong need for a basic implementation that - models the HTTP message and provides functions to send and receive them - over Asio. Such an implementation should serve as a building block upon - which higher abstractions such as the aforementioned HTTP service or - cgi-gateway can be built. - - One of the example programs implements a simple HTTP server that - delivers files from the filesystem. -]] - -[[ - "You should send a 100-continue to ask for the rest of the body if required." -][ - The Beast interface needs to support this functionality (by allowing this - special case of partial message parsing and serialization). Specifically, - it should let callers read the request up to just before the body, - and let callers write the request up to just before the body. However, - making use of this behavior should be up to callers (since Beast is low - level). -]] - -[[ - "What about HTTP/2?" -][ - Many reviewers feel that HTTP/2 support is an essential feature of - a HTTP library. The authors agree that HTTP/2 is important but also - feel that the most sensible implementation is one that does not re-use - the same network reading and writing interface for 2 as that for 1.0 - and 1.1. - - The Beast.HTTP message model is suitable for HTTP/2 and can be re-used. - The IETF HTTP Working Group adopted message compatiblity with HTTP/1.x - as an explicit goal. A parser can simply emit full headers after - decoding the compressed HTTP/2 headers. The stream ID is not logically - part of the message but rather message metadata and should be - communicated out-of-band (see below). HTTP/2 sessions begin with a - traditional HTTP/1.1 Upgrade similar in fashion to the WebSocket - upgrade. An HTTP/2 implementation can use existing Beast.HTTP primitives - to perform this handshake. - - Free functions for HTTP/2 sessions are not possible because of the - requirement to maintain per-session state. For example, to decode the - compressed headers. Or to remember and respect the remote peer's window - settings. The authors propose that a HTTP/2 implementation be written - as a separate class template, similar to the `websocket::stream` but with - additional interfaces to support version 2 features. We feel that - Beast.HTTP offers enough useful functionality to justify inclusion, - so that developers can take advantage of it right away instead of - waiting. -]] - -] - -[endsect] - - - -[section:websocket WebSocket FAQ] - -[variablelist - -[[ - What about message compression? -][ - Beast WebSocket supports the permessage-deflate extension described in - [@https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-00 draft-ietf-hybi-permessage-compression-00]. - The library comes with a header-only, C++11 port of ZLib's "deflate" codec - used in the implementation of the permessage-deflate extension. -]] - -[[ - Where is the TLS/SSL interface? -][ - The `websocket::stream` wraps the socket or stream that you provide - (for example, a `boost::asio::ip::tcp::socket` or a - `boost::asio::ssl::stream`). You establish your TLS connection using the - interface on `ssl::stream` like shown in all of the Asio examples, then - construct your `websocket::stream` around it. It works perfectly fine; - Beast.WebSocket doesn't try to reinvent the wheel or put a fresh coat of - interface paint on the `ssl::stream`. - - The WebSocket implementation [*does] provide support for shutting down - the TLS connection through the use of the ADL compile-time virtual functions - [link beast.ref.websocket__teardown `teardown`] and - [link beast.ref.websocket__async_teardown `async_teardown`]. These will - properly close the connection as per rfc6455 and overloads are available - for TLS streams. Callers may provide their own overloads of these functions - for user-defined next layer types. -]] - -] - -[endsect] - - - -[section:websocketpp Comparison to Zaphoyd Studios WebSocket++] - -[variablelist - -[[ - How does this compare to [@https://www.zaphoyd.com/websocketpp websocketpp], - an alternate header-only WebSocket implementation? -][ - [variablelist - - [[1. Synchronous Interface][ - - Beast offers full support for WebSockets using a synchronous interface. It - uses the same style of interfaces found in Boost.Asio: versions that throw - exceptions, or versions that return the error code in a reference parameter: - - [table - [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L774 Beast]] - [websocketpp] - ][ - [``` - template - void - read(opcode& op, DynamicBuffer& dynabuf) - ```] - [ - // - ] - ]]]] - - [[2. Connection Model][ - - websocketpp supports multiple transports by utilizing a trait, the `config::transport_type` - ([@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/asio/connection.hpp#L60 asio transport example]) - To get an idea of the complexity involved with implementing a transport, - compare the asio transport to the - [@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/iostream/connection.hpp#L59 `iostream` transport] - (a layer that allows websocket communication over a `std::iostream`). - - In contrast, Beast abstracts the transport by defining just one [*`NextLayer`] - template argument The type requirements for [*`NextLayer`] are - already familiar to users as they are documented in Asio: - __AsyncReadStream__, __AsyncWriteStream__, __SyncReadStream__, __SyncWriteStream__. - - The type requirements for instantiating `beast::websocket::stream` versus - `websocketpp::connection` with user defined types are vastly reduced - (18 functions versus 2). Note that websocketpp connections are passed by - `shared_ptr`. Beast does not use `shared_ptr` anywhere in its public interface. - A `beast::websocket::stream` is constructible and movable in a manner identical - to a `boost::asio::ip::tcp::socket`. Callers can put such objects in a - `shared_ptr` if they want to, but there is no requirement to do so. - - [table - [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp Beast]] - [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L234 websocketpp]] - ][ - [``` - template - class stream - { - NextLayer next_layer_; - ... - } - ```] - [``` - template - class connection - : public config::transport_type::transport_con_type - , public config::connection_base - { - public: - typedef lib::shared_ptr ptr; - ... - } - ```] - ]]]] - - [[3. Client and Server Role][ - - websocketpp provides multi-role support through a hierarchy of - different classes. A `beast::websocket::stream` is role-agnostic, it - offers member functions to perform both client and server handshakes - in the same class. The same types are used for client and server - streams. - - [table - [ - [Beast] - [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/roles/server_endpoint.hpp#L39 websocketpp], - [@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/roles/client_endpoint.hpp#L42 also]] - ][ - [ - // - ] - [``` - template - class client : public endpoint,config>; - template - class server : public endpoint,config>; - ```] - ]]]] - - [[4. Thread Safety][ - - websocketpp uses mutexes to protect shared data from concurrent - access. In contrast, Beast does not use mutexes anywhere in its - implementation. Instead, it follows the Asio pattern. Calls to - asynchronous initiation functions use the same method to invoke - intermediate handlers as the method used to invoke the final handler, - through the __asio_handler_invoke__ mechanism. - - The only requirement in Beast is that calls to asynchronous initiation - functions are made from the same implicit or explicit strand. For - example, if the `io_service` associated with a `beast::websocket::stream` - is single threaded, this counts as an implicit strand and no performance - costs associated with mutexes are incurred. - - [table - [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/impl/read_frame_op.ipp#L118 Beast]] - [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/iostream/connection.hpp#L706 websocketpp]] - ][ - [``` - template - friend - void asio_handler_invoke(Function&& f, read_frame_op* op) - { - return boost_asio_handler_invoke_helpers::invoke(f, op->d_->h); - } - ```] - [``` - mutex_type m_read_mutex; - ```] - ]]]] - - [[5. Callback Model][ - - websocketpp requires a one-time call to set the handler for each event - in its interface (for example, upon message receipt). The handler is - represented by a `std::function` equivalent. Its important to recognize - that the websocketpp interface performs type-erasure on this handler. - - In comparison, Beast handlers are specified in a manner identical to - Boost.Asio. They are function objects which can be copied or moved but - most importantly they are not type erased. The compiler can see - through the type directly to the implementation, permitting - optimization. Furthermore, Beast follows the Asio rules for treatment - of handlers. It respects any allocation, continuation, or invocation - customizations associated with the handler through the use of argument - dependent lookup overloads of functions such as `asio_handler_allocate`. - - The Beast completion handler is provided at the call site. For each - call to an asynchronous initiation function, it is guaranteed that - there will be exactly one final call to the handler. This functions - exactly the same way as the asynchronous initiation functions found in - Boost.Asio, allowing the composition of higher level abstractions. - - [table - [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L834 Beast]] - [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L281 websocketpp], - [@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L473 also]] - ][ - [``` - template - typename async_completion::result_type - async_read(opcode& op, DynamicBuffer& dynabuf, ReadHandler&& handler); - ```] - [``` - typedef lib::function message_handler; - void set_message_handler(message_handler h); - ```] - ]]]] - - [[6. Extensible Asynchronous Model][ - - Beast fully supports the - [@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3896.pdf Extensible Asynchronous Model] - developed by Christopher Kohlhoff, author of Boost.Asio (see Section 8). - - Beast websocket asynchronous interfaces may be used seamlessly with - `std::future` stackful/stackless coroutines, or user defined customizations. - - [table - [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/impl/stream.ipp#L378 Beast]] - [websocketpp] - ][ - [``` - beast::async_completion completion(handler); - read_op{ - completion.handler, *this, op, streambuf}; - return completion.result.get(); - ```] - [ - // - ] - ]]]] - - [[7. Message Buffering][ - - websocketpp defines a message buffer, passed in arguments by - `shared_ptr`, and an associated message manager which permits - aggregation and reuse of memory. The implementation of - `websocketpp::message` uses a `std::string` to hold the payload. If an - incoming message is broken up into multiple frames, the string may be - reallocated for each continuation frame. The `std::string` always uses - the standard allocator, it is not possible to customize the choice of - allocator. - - Beast allows callers to specify the object for receiving the message - or frame data, which is of any type meeting the requirements of - __DynamicBuffer__ (modeled after `boost::asio::streambuf`). - - Beast comes with the class __basic_streambuf__, an efficient - implementation of the __DynamicBuffer__ concept which makes use of multiple - allocated octet arrays. If an incoming message is broken up into - multiple pieces, no reallocation occurs. Instead, new allocations are - appended to the sequence when existing allocations are filled. Beast - does not impose any particular memory management model on callers. The - __basic_streambuf__ provided by beast supports standard allocators through - a template argument. Use the __DynamicBuffer__ that comes with beast, - customize the allocator if you desire, or provide your own type that - meets the requirements. - - [table - [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L774 Beast]] - [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/message_buffer/message.hpp#L78 websocketpp]] - ][ - [``` - template - read(opcode& op, DynamicBuffer& dynabuf); - ```] - [``` - template class con_msg_manager> - class message { - public: - typedef lib::shared_ptr ptr; - ... - std::string m_payload; - ... - }; - ```] - ]]]] - - [[8. Sending Messages][ - - When sending a message, websocketpp requires that the payload is - packaged in a `websocketpp::message` object using `std::string` as the - storage, or it requires a copy of the caller provided buffer by - constructing a new message object. Messages are placed onto an - outgoing queue. An asynchronous write operation runs in the background - to clear the queue. No user facing handler can be registered to be - notified when messages or frames have completed sending. - - Beast doesn't allocate or make copies of buffers when sending data. The - caller's buffers are sent in-place. You can use any object meeting the - requirements of - [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/ConstBufferSequence.html ConstBufferSequence], - permitting efficient scatter-gather I/O. - - The [*ConstBufferSequence] interface allows callers to send data from - memory-mapped regions (not possible in websocketpp). Callers can also - use the same buffers to send data to multiple streams, for example - broadcasting common subscription data to many clients at once. For - each call to `async_write` the completion handler is called once when - the data finishes sending, in a manner identical to `boost::asio::async_write`. - - [table - [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L1048 Beast]] - [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L672 websocketpp]] - ][ - [``` - template - void - write(ConstBufferSequence const& buffers); - ```] - [``` - lib::error_code send(std::string const & payload, - frame::opcode::value op = frame::opcode::text); - ... - lib::error_code send(message_ptr msg); - ```] - ]]]] - - [[9. Streaming Messages][ - - websocketpp requires that the entire message fit into memory, and that - the size is known ahead of time. - - Beast allows callers to compose messages in individual frames. This is - useful when the size of the data is not known ahead of time or if it - is not desired to buffer the entire message in memory at once before - sending it. For example, sending periodic output of a database query - running on a coroutine. Or sending the contents of a file in pieces, - without bringing it all into memory. - - [table - [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L1151 Beast]] - [websocketpp] - ][ - [``` - template - void - write_frame(bool fin, - ConstBufferSequence const& buffers); - ```] - [ - // - ] - ]]]] - - [[10. Flow Control][ - - The websocketpp read implementation continuously reads asynchronously - from the network and buffers message data. To prevent unbounded growth - and leverage TCP/IP's flow control mechanism, callers can periodically - turn this 'read pump' off and back on. - - In contrast a `beast::websocket::stream` does not independently begin - background activity, nor does it buffer messages. It receives data only - when there is a call to an asynchronous initiation function (for - example `beast::websocket::stream::async_read`) with an associated handler. - Applications do not need to implement explicit logic to regulate the - flow of data. Instead, they follow the traditional model of issuing a - read, receiving a read completion, processing the message, then - issuing a new read and repeating the process. - - [table - [ - [Beast] - [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L728 websocketpp]] - ][ - [ - // - ] - [``` - lib::error_code pause_reading(); - lib::error_code resume_reading(); - ```] - ]]]] - - [[11. Connection Establishment][ - - websocketpp offers the `endpoint` class which can handle binding and - listening to a port, and spawning connection objects. - - Beast does not reinvent the wheel here, callers use the interfaces - already in `boost::asio` for receiving incoming connections resolving - host names, or establishing outgoing connections. After the socket (or - `boost::asio::ssl::stream`) is connected, the `beast::websocket::stream` - is constructed around it and the WebSocket handshake can be performed. - - Beast users are free to implement their own "connection manager", but - there is no requirement to do so. - - [table - [ - [[@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/async_connect.html Beast], - [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/basic_socket_acceptor/async_accept.html also]] - [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/asio/endpoint.hpp#L52 websocketpp]] - ][ - [``` - #include - ```] - [``` - template - class endpoint : public config::socket_type; - ```] - ]]]] - - [[12. WebSocket Handshaking][ - - Callers invoke `beast::websocket::accept` to perform the WebSocket - handshake, but there is no requirement to use this function. Advanced - users can perform the WebSocket handshake themselves. Beast WebSocket - provides the tools for composing the request or response, and the - Beast HTTP interface provides the container and algorithms for sending - and receiving HTTP/1 messages including the necessary HTTP Upgrade - request for establishing the WebSocket session. - - Beast allows the caller to pass the incoming HTTP Upgrade request for - the cases where the caller has already received an HTTP message. - This flexibility permits novel and robust implementations. For example, - a listening socket that can handshake in multiple protocols on the - same port. - - Sometimes callers want to read some bytes on the socket before reading - the WebSocket HTTP Upgrade request. Beast allows these already-received - bytes to be supplied to an overload of the accepting function to permit - sophisticated features. For example, a listening socket that can - accept both regular WebSocket and Secure WebSocket (SSL) connections. - - [table - [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L501 Beast], - [@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L401 also]] - [websocketpp] - ][ - [``` - template - void - accept(ConstBufferSequence const& buffers); - - template - void - accept(http::request_v1 const& request); - ```] - [ - // - ] - ]]]] - - ] -]] - -] - -[endsect] - -[endsect] diff --git a/doc/docca/README.md b/doc/docca/README.md new file mode 100644 index 0000000000..ab7aa9ef0a --- /dev/null +++ b/doc/docca/README.md @@ -0,0 +1,4 @@ +# docca +Boost.Book XSLT C++ documentation system + +[Example Documentation](http://vinniefalco.github.io/docca/) diff --git a/doc/docca/example/.gitignore b/doc/docca/example/.gitignore new file mode 100644 index 0000000000..fc40be018b --- /dev/null +++ b/doc/docca/example/.gitignore @@ -0,0 +1,5 @@ +bin +html +temp +reference.qbk +out.txt diff --git a/doc/docca/example/Jamfile b/doc/docca/example/Jamfile new file mode 100644 index 0000000000..61d564f076 --- /dev/null +++ b/doc/docca/example/Jamfile @@ -0,0 +1,65 @@ +# +# Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the 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 ; + +local broot = [ os.environ BOOST_ROOT ] ; + +project docca/doc ; + +using boostbook ; +using quickbook ; +using doxygen ; + +xml docca_bb : main.qbk ; + +path-constant out : . ; + +install stylesheets + : + $(broot)/doc/src/boostbook.css + : + $(out)/html + ; + +explicit stylesheets ; + +install images + : + [ glob $(broot)/doc/src/images/*.png ] + : + $(out)/html/images + ; + +explicit images ; + +install callouts + : + [ glob $(broot)/doc/src/images/callouts/*.png ] + : + $(out)/html/images/callouts + ; + +explicit callout ; + +boostbook doc + : + docca_bb + : + chapter.autolabel=0 + boost.root=$(broot) + chapter.autolabel=0 + chunk.first.sections=1 # Chunk the first top-level section? + chunk.section.depth=8 # Depth to which sections should be chunked + generate.section.toc.level=1 # Control depth of TOC generation in sections + toc.max.depth=2 # How many levels should be created for each TOC? + toc.section.depth=2 # How deep should recursive sections appear in the TOC? + : + temp + stylesheets + images + ; diff --git a/doc/docca/example/boostbook.dtd b/doc/docca/example/boostbook.dtd new file mode 100644 index 0000000000..bd4c3f871e --- /dev/null +++ b/doc/docca/example/boostbook.dtd @@ -0,0 +1,439 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +%DocBook; diff --git a/doc/docca/example/include/docca/example.hpp b/doc/docca/example/include/docca/example.hpp new file mode 100644 index 0000000000..daf1a47565 --- /dev/null +++ b/doc/docca/example/include/docca/example.hpp @@ -0,0 +1,851 @@ +// +// Copyright (c) 2015-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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 EXAMPLE_HPP +#define EXAMPLE_HPP + +#include +#include + +// This is a sample header file to show docca XLST results +// +// namespace, enum, type alias, global, static global, +// function, static function, struct/class + +namespace example { + +/** Enum + + Description +*/ +enum enum_t +{ + /// 0 + zero, + + /// 1 + one, + + /// 2 + two +}; + +/** Enum class + + Description +*/ +enum class enum_c +{ + /// aaa + aaa, + + /// bbb + bbb, + + /// ccc + ccc +}; + +/** Type alias + + Description +*/ +using type = std::string; + +/** Template type alias + + Description +*/ +template +using t_type = std::vector; + +/** Void or deduced + + Description +*/ +using vod = void_or_deduced; + +/** Implementation-defined + + Description +*/ +using impdef = implementation_defined; + +/** Variable + + Description +*/ +extern std::size_t var; + +/** Static variable + + Description +*/ +static std::size_t s_var = 0; + +/** Brief with @b bold text. + + Function returning @ref type. + + @return The type + + @see t_func. + + @throw std::exception on error + @throw std::domain_error on bad parameters + + @par Thread Safety + + Cannot be called concurrently. + + @note Additional notes. + + @param arg1 Function parameter 1 + @param arg2 Function parameter 2 +*/ +type +func(int arg1, std::string arg2); + +/** Brief for function starting with _ + + @return @ref type + + @see func +*/ +type +_func(float arg1, std::size arg2); + +/** Brief. + + Function description. + + See @ref func. + + @tparam T Template parameter 1 + @tparam U Template parameter 2 + @tparam V Template parameter 3 + + @param t Function parameter 1 + @param u Function parameter 2 + @param v Function parameter 3 + + @return nothing +*/ +template +void +t_func(T t, U const& u, V&& v); + +/** Overloaded function 1 + + Description + + @param arg1 Parameter 1 +*/ +void +overload(int arg1); + +/** Overloaded function 2 + + Description + + @param arg1 Parameter 1 + @param arg2 Parameter 2 +*/ +void +overload(int arg1, int arg2); + +/** Overloaded function 3 + + Description + + @param arg1 Parameter 1 + @param arg2 Parameter 2 + @param arg3 Parameter 3 +*/ +void +overload(int arg1, int arg2, int arg3); + +/** Markdown examples + + @par List + + 1. Lists with extra long lines that can *span* multiple lines + and overflow even the longest of buffers. + 2. With Numbers + + Or not + + Nesting + 1. Deeply + + And returning `here`. + + Another list I enjoy: + + -# 1 + - 1.a + -# 1.a.1 + -# 1.a.2 + - 1.b + -# 2 + - 2.a + - 2.b + -# 2.b.1 + -# 2.b.2 + - 2.b.2.a + - 2.b.2.b + + @par Table + + First Header | Second Header + ------------- | ------------- + Content Cell | Content Cell + Content Cell | Content Cell +*/ +void markdown(); + +//------------------------------------------------------------------------------ + +namespace detail { + +/** Detail class + + Description +*/ +struct detail_type +{ +}; + +/** Detail function + + Description +*/ +void +detail_function(); + +} // detail + +//------------------------------------------------------------------------------ + +/// Nested namespace +namespace nested { + +/** Enum + + Description +*/ +enum enum_t +{ + /// 0 + zero, + + /// 1 + one, + + /// 2 + two +}; + +/** Enum class + + Description +*/ +enum class enum_c +{ + /// aaa + aaa, + + /// bbb + bbb, + + /// ccc + ccc +}; + +/** Type alias + + Description +*/ +using type = std::string; + +/** Template type alias + + Description +*/ +template +using t_type = std::vector; + +/** Variable + + Description +*/ +extern std::size_t var; + +/** Static variable + + Description +*/ +static std::size_t s_var = 0; + +/** Brief with @b bold text. + + Function returning @ref type. + + @return The type + + @see t_func. + + @throw std::exception on error + @throw std::domain_error on bad parameters + + @par Thread Safety + + Cannot be called concurrently. + + @note Additional notes. + + @param arg1 Function parameter 1 + @param arg2 Function parameter 2 +*/ +type +func(int arg1, std::string arg2); + +/** Brief for function starting with _ + +@return @ref type + +@see func +*/ +type +_func(float arg1, std::size arg2); + +/** Brief. + + Function description. + + See @ref func. + + @tparam T Template parameter 1 + @tparam U Template parameter 2 + @tparam V Template parameter 3 + + @param t Function parameter 1 + @param u Function parameter 2 + @param v Function parameter 3 + + @return nothing +*/ +template +void +t_func(T t, U const& u, V&& v); + +/** Overloaded function 1 + + Description + + @param arg1 Parameter 1 +*/ +void +overload(int arg1); + +/** Overloaded function 2 + + Description + + @param arg1 Parameter 1 + @param arg2 Parameter 2 +*/ +void +overload(int arg1, int arg2); + +/** Overloaded function 3 + + Description + + @param arg1 Parameter 1 + @param arg2 Parameter 2 + @param arg3 Parameter 3 +*/ +void +overload(int arg1, int arg2, int arg3); + +} // nested + +/// Overloads operators +struct Num +{ + + /// Addition + friend + Num + operator +(Num, Num); + + /// Subtraction + friend + Num + operator -(Num, Num); + + /// Multiplication + friend + Num + operator *(Num, Num); + + /// Division + friend + Num + operator /(Num, Num); + +}; + +/// @ref Num addition +Num +operator +(Num, Num); + +/// @ref Num subtraction +Num +operator -(Num, Num); + +/// @ref Num multiplication +Num +operator *(Num, Num); + +/// @ref Num division +Num +operator /(Num, Num); + +/** Template class type. + + Description. + + @tparam T Template parameter 1 + @tparam U Template parameter 2 +*/ +template +class class_type +{ +public: + /** Enum + + Description + */ + enum enum_t + { + /// 0 + zero, + + /// 1 + one, + + /// 2 + two, + + /// _3 + _three + }; + + /** Enum class + + Description + */ + enum class enum_c + { + /// aaa + aaa, + + /// bbb + bbb, + + /// ccc + ccc, + + /// _ddd + _ddd + }; + + /** Type alias + + Description + */ + using type = std::string; + + /** Template type alias + + Description + */ + template + using t_type = std::vector; + + /** Variable + + Description + */ + extern std::size_t var; + + /** Static variable + + Description + */ + static std::size_t s_var = 0; + + /** Default Ctor + + Description + */ + class_type(); + + /** Dtor + + Description + */ + ~class_type(); + + /** Brief with @b bold text. + + Function returning @ref type. + + @return The type + + @see t_func. + + @throw std::exception on error + @throw std::domain_error on bad parameters + + @par Thread Safety + + Cannot be called concurrently. + + @note Additional notes. + + @param arg1 Function parameter 1 + @param arg2 Function parameter 2 + */ + type + func(int arg1, std::string arg2); + + /** Brief. + + Function description. + + See @ref func. + + @tparam T Template parameter 1 + @tparam U Template parameter 2 + @tparam V Template parameter 3 + + @param t Function parameter 1 + @param u Function parameter 2 + @param v Function parameter 3 + + @return nothing + */ + template + void + t_func(T t, U const& u, V&& v); + + /** Overloaded function 1 + + Description + + @param arg1 Parameter 1 + */ + void + overload(int arg1); + + /** Overloaded function 2 + + Description + + @param arg1 Parameter 1 + @param arg2 Parameter 2 + */ + void + overload(int arg1, int arg2); + + /** Overloaded function 3 + + Description + + @param arg1 Parameter 1 + @param arg2 Parameter 2 + @param arg3 Parameter 3 + */ + void + overload(int arg1, int arg2, int arg3); + + /** Less-than operator + + Description + */ + bool + operator< (class_type const& rhs) const; + + /** Greater-than operator + + Description + */ + bool + operator> (class_type const& rhs) const; + + /** Less-than-or-equal-to operator + + Description + */ + bool + operator<= (class_type const& rhs) const; + + /** Greater-than-or-equal-to operator + + Description + */ + bool + operator>= (class_type const& rhs) const; + + /** Equality operator + + Description + */ + bool + operator== (class_type const& rhs) const; + + /** Inequality operator + + Description + */ + bool + operator!= (class_type const& rhs) const; + + /** Arrow operator + + Description + */ + std::size_t operator->() const; + + /** Index operator + + Description + */ + enum_c& operator[](std::size_t); + + /** Index operator + + Description + */ + enum_c operator[](std::size_t) const; + + /// Public data + std::size_t pub_data_; + + /// Public static data + static std::size_t pub_sdata_; + +protected: + /** Protected data + + Description + */ + std::size_t prot_data_; + + /** Protected enum + + Description + */ + enum_c _prot_enum; + + /** Static protected data + + Description + */ + static std::size_t prot_sdata_; + + /** Protected type + + Description + */ + struct prot_type + { + }; + + /** Protected function + + Description + */ + void prot_memfn(); + + /** Protected function returning @ref prot_type + + Description + */ + prot_type prot_rvmemfn(); + + /** Protected static member function + + Description + */ + static void static_prot_memfn(); + +private: + /** Private data + + Description + */ + std::size_t priv_data_; + + /** Static private data + + Description + */ + static std::size_t priv_sdata_; + + /** Private type + + Description + */ + struct priv_type + { + }; + + /** Private function + + Description + */ + void priv_memfn(); + + /** Private function returning *ref priv_type + + Description + */ + priv_type priv_rvmemfn(); + + /** Static private member function + + Description + */ + static void static_priv_memfn(); + + /** Friend class + + Description + */ + friend friend_class; +}; + +/// Other base class 1 +class other_base_class1 +{ +}; + +/// Other base class 2 +class other_base_class2 +{ +}; + +/** Derived type + + Description +*/ +template +class derived_type : + public class_type, + protected other_base_class1, + private other_base_class2 +{ +}; + +/** References to all identifiers: + + Description one @ref one + + @par See Also + + @li @ref type + + @li @ref t_type + + @li @ref vod + + @li @ref impdef + + @li @ref var + + @li @ref s_var + + @li @ref func + + @li @ref t_func + + @li @ref overload + + @li @ref nested::enum_t : @ref nested::zero @ref nested::one @ref nested::two + + @li @ref nested::enum_c : nested::enum_c::aaa @ref nested::enum_c::bbb @ref nested::enum_c::ccc + + @li @ref nested::type + + @li @ref nested::t_type + + @li @ref nested::var + + @li @ref nested::s_var + + @li @ref nested::func + + @li @ref nested::t_func + + @li @ref nested::overload + + @li @ref class_type + + @li @ref class_type::enum_t : @ref class_type::zero @ref class_type::one @ref class_type::two @ref class_type::_three + + @li @ref class_type::enum_c : class_type::enum_c::aaa @ref class_type::enum_c::bbb @ref class_type::enum_c::ccc class_type::enum_c::_ddd + + @li @ref class_type::type + + @li @ref class_type::t_type + + @li @ref class_type::var + + @li @ref class_type::s_var + + @li @ref class_type::class_type + + @li @ref class_type::func + + @li @ref class_type::t_func + + @li @ref class_type::overload + + @li @ref class_type::pub_data_ + + @li @ref class_type::pub_sdata_ + + @li @ref class_type::_prot_enum + + @li @ref class_type::prot_type + + @li @ref class_type::priv_type + + @li @ref derived_type + + @li @ref Num + +*/ +void all_ref(); + +} // example + +namespace other { + +/// other function +void func(); + +/// other class +struct class_type +{ +}; + + +} // other + +#endif diff --git a/doc/docca/example/index.xml b/doc/docca/example/index.xml new file mode 100644 index 0000000000..c364e4ed26 --- /dev/null +++ b/doc/docca/example/index.xml @@ -0,0 +1,14 @@ + + + + + +
+ Index + +
diff --git a/doc/docca/example/main.qbk b/doc/docca/example/main.qbk new file mode 100644 index 0000000000..43ddf6ae18 --- /dev/null +++ b/doc/docca/example/main.qbk @@ -0,0 +1,28 @@ +[/ + Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[library docca + [quickbook 1.6] + [copyright 2016 Vinnie Falco] + [purpose Documentation Library] + [license + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + [@http://www.boost.org/LICENSE_1_0.txt]) + ] + [category template] + [category generic] +] + +[template mdash[] '''— '''] +[template indexterm1[term1] ''''''[term1]''''''] +[template indexterm2[term1 term2] ''''''[term1]''''''[term2]''''''] + +[section:ref Reference] +[include reference.qbk] +[endsect] +[xinclude index.xml] diff --git a/doc/docca/example/makeqbk.sh b/doc/docca/example/makeqbk.sh new file mode 100644 index 0000000000..e6fa0c30a7 --- /dev/null +++ b/doc/docca/example/makeqbk.sh @@ -0,0 +1,13 @@ +#!/usr/bin/bash + +# Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +mkdir -p temp +doxygen source.dox +cd temp +xsltproc combine.xslt index.xml > all.xml +xsltproc ../reference.xsl all.xml > ../reference.qbk + diff --git a/doc/docca/example/reference.xsl b/doc/docca/example/reference.xsl new file mode 100644 index 0000000000..de56752943 --- /dev/null +++ b/doc/docca/example/reference.xsl @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/doc/docca/example/source.dox b/doc/docca/example/source.dox new file mode 100644 index 0000000000..c55616ee7b --- /dev/null +++ b/doc/docca/example/source.dox @@ -0,0 +1,333 @@ +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = "docca" +PROJECT_NUMBER = +PROJECT_BRIEF = Documentation Library +PROJECT_LOGO = +OUTPUT_DIRECTORY = +CREATE_SUBDIRS = NO +ALLOW_UNICODE_NAMES = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = +ALWAYS_DETAILED_SEC = YES +INLINE_INHERITED_MEMB = YES +FULL_PATH_NAMES = NO +STRIP_FROM_PATH = include/ +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = YES +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = YES +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 4 +ALIASES = +TCL_SUBST = +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +OPTIMIZE_FOR_FORTRAN = NO +OPTIMIZE_OUTPUT_VHDL = NO +EXTENSION_MAPPING = +MARKDOWN_SUPPORT = YES +AUTOLINK_SUPPORT = YES +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +SIP_SUPPORT = NO +IDL_PROPERTY_SUPPORT = YES +DISTRIBUTE_GROUP_DOC = NO +GROUP_NESTED_COMPOUNDS = NO +SUBGROUPING = YES +INLINE_GROUPED_CLASSES = NO +INLINE_SIMPLE_STRUCTS = NO +TYPEDEF_HIDES_STRUCT = NO +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = YES +EXTRACT_PRIVATE = YES +EXTRACT_PACKAGE = NO +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = NO +EXTRACT_LOCAL_METHODS = NO +EXTRACT_ANON_NSPACES = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +HIDE_COMPOUND_REFERENCE= NO +SHOW_INCLUDE_FILES = NO +SHOW_GROUPED_MEMB_INC = NO +FORCE_LOCAL_INCLUDES = NO +INLINE_INFO = NO +SORT_MEMBER_DOCS = NO +SORT_BRIEF_DOCS = NO +SORT_MEMBERS_CTORS_1ST = YES +SORT_GROUP_NAMES = NO +SORT_BY_SCOPE_NAME = NO +STRICT_PROTO_MATCHING = NO +GENERATE_TODOLIST = NO +GENERATE_TESTLIST = NO +GENERATE_BUGLIST = NO +GENERATE_DEPRECATEDLIST= NO +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = NO +SHOW_FILES = NO +SHOW_NAMESPACES = NO +FILE_VERSION_FILTER = +LAYOUT_FILE = +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_AS_ERROR = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = include/docca/example.hpp + +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = +RECURSIVE = NO +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = +EXAMPLE_RECURSIVE = YES +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +FILTER_SOURCE_PATTERNS = +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +REFERENCES_LINK_SOURCE = YES +SOURCE_TOOLTIPS = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +CLANG_ASSISTED_PARSING = NO +CLANG_OPTIONS = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 1 +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = NO +HTML_OUTPUT = dhtm +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_EXTRA_STYLESHEET = +HTML_EXTRA_FILES = +HTML_COLORSTYLE_HUE = 220 +HTML_COLORSTYLE_SAT = 100 +HTML_COLORSTYLE_GAMMA = 80 +HTML_TIMESTAMP = NO +HTML_DYNAMIC_SECTIONS = NO +HTML_INDEX_NUM_ENTRIES = 100 +GENERATE_DOCSET = NO +DOCSET_FEEDNAME = "Doxygen generated docs" +DOCSET_BUNDLE_ID = org.doxygen.Project +DOCSET_PUBLISHER_ID = org.doxygen.Publisher +DOCSET_PUBLISHER_NAME = Publisher +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +CHM_INDEX_ENCODING = +BINARY_TOC = NO +TOC_EXPAND = NO +GENERATE_QHP = NO +QCH_FILE = +QHP_NAMESPACE = org.doxygen.Project +QHP_VIRTUAL_FOLDER = doc +QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = +QHG_LOCATION = +GENERATE_ECLIPSEHELP = NO +ECLIPSE_DOC_ID = org.doxygen.Project +DISABLE_INDEX = NO +GENERATE_TREEVIEW = NO +ENUM_VALUES_PER_LINE = 4 +TREEVIEW_WIDTH = 250 +EXT_LINKS_IN_WINDOW = NO +FORMULA_FONTSIZE = 10 +FORMULA_TRANSPARENT = YES +USE_MATHJAX = NO +MATHJAX_FORMAT = HTML-CSS +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest +MATHJAX_EXTENSIONS = +MATHJAX_CODEFILE = +SEARCHENGINE = YES +SERVER_BASED_SEARCH = NO +EXTERNAL_SEARCH = NO +SEARCHENGINE_URL = +SEARCHDATA_FILE = searchdata.xml +EXTERNAL_SEARCH_ID = +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4 +EXTRA_PACKAGES = +LATEX_HEADER = +LATEX_FOOTER = +LATEX_EXTRA_STYLESHEET = +LATEX_EXTRA_FILES = +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +LATEX_SOURCE_CODE = NO +LATEX_BIB_STYLE = plain +LATEX_TIMESTAMP = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +RTF_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_SUBDIR = +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = YES +XML_OUTPUT = temp/ +XML_PROGRAMLISTING = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- +GENERATE_DOCBOOK = NO +DOCBOOK_OUTPUT = docbook +DOCBOOK_PROGRAMLISTING = NO + +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = YES +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = DOXYGEN \ + GENERATING_DOCS \ + _MSC_VER + +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +EXTERNAL_PAGES = YES +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = NO +MSCGEN_PATH = +DIA_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = NO +DOT_NUM_THREADS = 0 +DOT_FONTNAME = Helvetica +DOT_FONTSIZE = 10 +DOT_FONTPATH = +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +UML_LIMIT_NUM_FIELDS = 10 +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +INTERACTIVE_SVG = NO +DOT_PATH = +DOTFILE_DIRS = +MSCFILE_DIRS = +DIAFILE_DIRS = +PLANTUML_JAR_PATH = +PLANTUML_INCLUDE_PATH = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 0 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES diff --git a/doc/reference.xsl b/doc/docca/include/docca/doxygen.xsl similarity index 74% rename from doc/reference.xsl rename to doc/docca/include/docca/doxygen.xsl index 7a4420983e..aca6707c57 100644 --- a/doc/reference.xsl +++ b/doc/docca/include/docca/doxygen.xsl @@ -2,6 +2,8 @@ -[/ - Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + [/ + Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -39,19 +41,12 @@ - + - + @@ -61,17 +56,28 @@ - + + + + + + + + + + + + + + + + + + - - - - - - - - + + @@ -79,12 +85,23 @@ + + + + + + + + + + + + + + - - - @@ -108,6 +125,7 @@ + @@ -162,7 +180,7 @@ ``['implementation-defined]`` - __void_or_deduced__ + ``[@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/asynchronous_operations.html#boost_asio.reference.asynchronous_operations.return_type_of_an_initiating_function ['void-or-deduced]]`` @@ -195,18 +213,6 @@ select="concat(substring-before($name, '::'), '__', substring-after($name, '::'))"/> - - - - - - - - - - + + + + + - + + select="concat(substring-before($name, '/'), '_slash_', substring-after($name, '/'))"/> + + + + + @@ -319,7 +337,7 @@ - + [heading ] @@ -394,41 +412,91 @@ ` + + + + + + + + + - * - - - - + + + + + + + + + # + + + * + + + [*] ['] + - - [heading Parameters] - [heading Exceptions] + [table [[Type][Thrown On]] + + [heading Parameters] + [table [[Name][Description]] + + + [heading Template Parameters] + [table [[Type][Description]] + + + [table [[Name][Description]] + - [variablelist ] + - [[ + [[` - ][ + `][ ]] + + + [table + + ] + + + + [ + + ] + + + + [ + + ] + @@ -586,120 +654,167 @@ - - - - - - - - - - - - - - - - - - - - [link beast.ref. - - ` - - `] - - - [role red |1| - - ] - - - - - - - + + + + - - - - - - + + + + + + + + + + + + + :: + + + + + + + |1a| + [link + + ` + + `] + + [heading Debug] [table [[name][value]] + + + + + + + + + + + + + + - - + + + + + :: + + + + + + - [link beast.ref. - - - - - - - - + |1b| + [link + ` - + `] - [link beast.ref. - + |1c| + [link + ` - + `] [role red - + |1| + + ] - - - [role red - - ] + + [heading Debug] [table [[name][value]] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + |2| + [link + + ` + + `] + + + [role red + |2| + + ] + + + + [heading Debug] [table [[name][value]] + + + + + + + + + + - [role red |3| + [role red + |6| ] @@ -717,28 +832,22 @@ - + + - - - [link beast.ref. + [link @@ -752,7 +861,7 @@ `] - [link beast.ref. + [link ` @@ -760,6 +869,7 @@ [role red + |8| ] @@ -767,6 +877,7 @@ [role red + |9| ] @@ -775,38 +886,29 @@ - + - - [heading Requirements] - ['Header: ][^ - - ] - - - ['Convenience header: ][^beast/core.hpp] - - - ['Convenience header: ][^beast/http.hpp] - - - ['Convenience header: ][^beast/websocket.hpp] - - - - - - - - + + + + + + + + + + + + + - + @@ -817,7 +919,7 @@ - + @@ -827,21 +929,23 @@ ] + [heading Synopsis] + + + ``` - + : - - public - - + + @@ -854,15 +958,16 @@ + [heading Description] - - - + + + [endsect] @@ -874,7 +979,7 @@ sectiondef[@kind='public-type'] | innerclass[@prot='public' and not(contains(., '_handler'))]) > 0"> [heading Types] - [table [[Name][Description]] + [table [[Name][Description]] @@ -882,7 +987,8 @@ [ - [[link beast.ref. + [[link + . @@ -896,25 +1002,20 @@ - - - - - - + - [[link beast.ref. - + [[link + [* - + ]]] [ ] @@ -924,9 +1025,9 @@ ] - + [heading Member Functions] - [table [[Name][Description]] + [table [[Name][Description]] @@ -957,7 +1058,7 @@ [ - [[link beast.ref. + [[link . [* @@ -974,9 +1075,9 @@ ] - + [heading Protected Member Functions] - [table [[Name][Description]] + [table [[Name][Description]] @@ -1002,7 +1103,7 @@ [ - [[link beast.ref. + [[link . [* @@ -1019,9 +1120,9 @@ ] - + [heading Private Member Functions] - [table [[Name][Description]] + [table [[Name][Description]] @@ -1047,7 +1148,7 @@ [ - [[link beast.ref. + [[link . [* @@ -1064,29 +1165,13 @@ ] - - [heading Static Data Members] - [table [[Name][Description]] - - - [ - [[link beast.ref. - . - [* - - ]]] [ - - ] ] - - ] - - + [heading Data Members] - [table [[Name][Description]] - + [table [[Name][Description]] + [ - [[link beast.ref. + [[link . [* @@ -1096,13 +1181,13 @@ ] - + [heading Protected Data Members] - [table [[Name][Description]] + [table [[Name][Description]] [ - [[link beast.ref. + [[link . [* @@ -1112,13 +1197,14 @@ ] - + [heading Private Data Members] - [table [[Name][Description]] + [table [[Name][Description]] [ - [[link beast.ref. + [[link . [* @@ -1130,7 +1216,7 @@ [heading Friends] - [table [[Name][Description]] + [table [[Name][Description]] @@ -1156,7 +1242,7 @@ [ - [[link beast.ref. + [[link . [* @@ -1175,7 +1261,7 @@ [heading Related Functions] - [table [[Name][Description]] + [table [[Name][Description]] @@ -1201,7 +1287,7 @@ [ - [[link beast.ref.. + [[link . [*]]] [ @@ -1227,12 +1313,53 @@ - - - - - - + + + + + + + + + + + + + + + + + + @@ -1315,7 +1442,7 @@ - ``[link beast.ref. + ``[link . @@ -1330,12 +1457,22 @@ const ; + + ``[''''&raquo;''' + [link + + . + + .overload + + more...]]`` + + ``` - [section: @@ -1359,11 +1496,12 @@ - ['Inherited from - + (Inherited from ` + + - .] + `) @@ -1374,6 +1512,12 @@ ] + [heading Synopsis] + + + + + @@ -1399,15 +1543,16 @@ ``` + [heading Description] - - + + - [endsect] [endsect] + [endsect] [endsect] [endsect] @@ -1439,17 +1584,23 @@ + - - - - - + @@ -1459,7 +1610,7 @@ - + ``` @@ -1494,12 +1645,14 @@ [heading Values] - [variablelist + [table [[Name][Description]] - [[ + [[[^ - ] [ + ]][ + + ]] ] @@ -1552,56 +1705,7 @@ - - class ``[link beast.ref.streams.AsyncStream [*AsyncStream]]`` - - - class __AsyncReadStream__ - - - class __AsyncWriteStream__ - - - class ``[link beast.ref.Body [*Body]]`` - - - class ``[link beast.ref.BufferSequence [*BufferSequence]]`` - - - - ``[link beast.ref.BufferSequence [*BufferSequence]]`` - - - class __CompletionHandler__ - - - class __ConstBufferSequence__ - - - class ``[link beast.ref.DynamicBuffer [*DynamicBuffer]]`` - - - class __Handler__ - - - class __MutableBufferSequence__ - - - class ``[link beast.ref.Parser [*Parser]]`` - - - class ``[link beast.ref.streams.Stream [*Stream]]`` - - - class ``[link beast.ref.streams.SyncStream [*SyncStream]]`` - - - class __SyncReadStream__ - - - class __SyncWriteStream__ - - + @@ -1611,7 +1715,7 @@ - + @@ -1621,7 +1725,7 @@ - + = @@ -1644,13 +1748,13 @@ - + - + = @@ -1665,7 +1769,7 @@ - + @@ -1676,7 +1780,7 @@ - + @@ -1692,15 +1796,25 @@ + + + + + [section: - + ] [indexterm1 - - ] + + ] + @@ -1731,7 +1845,7 @@ - ``[link beast.ref. + ``[link .overload @@ -1739,15 +1853,21 @@ ]``( - ); + ); + + ``[''''&raquo;''' + [link + + .overload + + more...]]`` + + ``` - - - [section: @@ -1774,29 +1894,50 @@ ] - - - - - - - - - - - - - - - - ``` - - ``` - - - - - + [heading Synopsis] + + + + + + + + + + + + + + + + + + + + + + + ``` + + ``` + + + [heading Description] + + + [heading Debug] [table [[name][value]] + + + + + + + + + + + + diff --git a/doc/examples.qbk b/doc/examples.qbk deleted file mode 100644 index 224f4d9fdc..0000000000 --- a/doc/examples.qbk +++ /dev/null @@ -1,136 +0,0 @@ -[/ - 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) -] - -[section:example Examples] - -These usage examples are intended to quickly impress upon readers the -flavor of the library. They are complete programs which may be built -and run. Source code and build scripts for these programs may be found -in the examples directory. - -[heading HTTP GET] - -Use HTTP to request the root page from a website and print the response: - -``` -#include -#include -#include -#include -#include - -int main() -{ - // Normal boost::asio setup - std::string const host = "boost.org"; - boost::asio::io_service ios; - boost::asio::ip::tcp::resolver r{ios}; - boost::asio::ip::tcp::socket sock{ios}; - boost::asio::connect(sock, - r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"})); - - // Send HTTP request using beast - beast::http::request req; - req.method = "GET"; - req.url = "/"; - req.version = 11; - req.fields.replace("Host", host + ":" + - boost::lexical_cast(sock.remote_endpoint().port())); - req.fields.replace("User-Agent", "Beast"); - beast::http::prepare(req); - beast::http::write(sock, req); - - // Receive and print HTTP response using beast - beast::streambuf sb; - beast::http::response resp; - beast::http::read(sock, sb, resp); - std::cout << resp; -} -``` -[heading WebSocket] - -Establish a WebSocket connection, send a message and receive the reply: -``` -#include -#include -#include -#include -#include - -int main() -{ - // Normal boost::asio setup - std::string const host = "echo.websocket.org"; - boost::asio::io_service ios; - boost::asio::ip::tcp::resolver r{ios}; - boost::asio::ip::tcp::socket sock{ios}; - boost::asio::connect(sock, - r.resolve(boost::asio::ip::tcp::resolver::query{host, "80"})); - - // WebSocket connect and send message using beast - beast::websocket::stream ws{sock}; - ws.handshake(host, "/"); - ws.write(boost::asio::buffer(std::string("Hello, world!"))); - - // Receive WebSocket message, print and close using beast - beast::streambuf sb; - beast::websocket::opcode op; - ws.read(op, sb); - ws.close(beast::websocket::close_code::normal); - std::cout << beast::to_string(sb.data()) << "\n"; -} -``` - -[heading WebSocket Echo Server] - -This example demonstrates both synchronous and asynchronous -WebSocket server implementations. - -* [@examples/websocket_async_echo_server.hpp] -* [@examples/websocket_sync_echo_server.hpp] -* [@examples/websocket_echo.cpp] - -[heading Secure WebSocket] - -Establish a WebSocket connection over an encrypted TLS connection, -send a message and receive the reply. Requires OpenSSL to build. - -* [@examples/websocket_ssl_example.cpp] - -[heading HTTPS GET] - -This example demonstrates sending and receiving HTTP messages -over a TLS connection. Requires OpenSSL to build. - -* [@examples/http_ssl_example.cpp] - -[heading HTTP Crawl] - -This example retrieves the page at each of the most popular domains -as measured by Alexa. - -* [@examples/http_crawl.cpp] - -[heading HTTP Server] - -This example demonstrates both synchronous and asynchronous server -implementations. It also provides an example of implementing a [*Body] -type, in `file_body`. - -* [@examples/file_body.hpp] -* [@examples/http_async_server.hpp] -* [@examples/http_sync_server.hpp] -* [@examples/http_server.cpp] - -[heading Listings] - -These are stand-alone listings of the HTTP and WebSocket examples. - -* [@examples/http_example.cpp] -* [@examples/websocket_example.cpp] - -[endsect] diff --git a/doc/http.qbk b/doc/http.qbk deleted file mode 100644 index 9249fd64fb..0000000000 --- a/doc/http.qbk +++ /dev/null @@ -1,381 +0,0 @@ -[/ - 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) -] - -[/ -ideas: - - complete send request walkthrough (client) - - complete receive response walkthrough (client) - - complete receive request walkthrough (server) - - complete send response walkthrough (server) - - - Introduce concepts from simple to complex - - Smooth progression of new ideas building on the previous ideas - - - do we show a simplified message with collapsed fields? - - do we introduce `header` or `message` first? - - -contents: - Message (and header, fields) - Create request - Create response - Algorithms - Write - Read - Parse - Examples - Send Request - Receive Response - Receive Request - Send Response - Advanced - Responding to HEAD - Expect: 100-continue - Body (user defined) - - -section beast.http.examples Examples - -note - In the example code which follows, `socket` refers to an object of type - `boost::asio::ip::tcp::socket` which is currently connected to a remote peer. -] - - - -[section:http Using HTTP] - -[block ''' - - Message - Fields - Body - Algorithms - -'''] - -Beast offers programmers simple and performant models of HTTP messages and -their associated operations including synchronous and asynchronous reading and -writing of messages and headers in the HTTP/1 wire format using Boost.Asio. - -[note - The following documentation assumes familiarity with both Boost.Asio - and the HTTP protocol specification described in __rfc7230__. Sample code - and identifiers mentioned in this section are written as if the following - declarations are in effect: - ``` - #include - #include - using namespace beast; - using namespace beast::http; - ``` -] - - - - - -[section:message Message] - -The HTTP protocol defines the client and server roles: clients send messages -called requests and servers send back messages called responses. A HTTP message -(referred to hereafter as "message") contains request or response specific -attributes (contained in the "Start Line"), a series of zero or more name/value -pairs (collectively termed "Fields"), and an optional series of octets called -the message body which may be zero in length. The start line for a HTTP request -includes a string called the method, a string called the URL, and a version -number indicating HTTP/1.0 or HTTP/1.1. For a response, the start line contains -an integer status code and a string called the reason phrase. Alternatively, a -HTTP message can be viewed as two parts: a header, followed by a body. - -[note - The Reason-Phrase is obsolete as of rfc7230. -] - -The __header__ class template models the header for HTTP/1 and HTTP/2 messages. -This class template is a family of specializations, one for requests and one -for responses, depending on the [*`isRequest`] template value. -The [*`Fields`] template type determines the type of associative container -used to store the field values. The provided __basic_fields__ class template -and __fields__ type alias are typical choices for the [*`Fields`] type, but -advanced applications may supply user defined types which meet the requirements. -The __message__ class template models the header and optional body for HTTP/1 -and HTTP/2 requests and responses. It is derived from the __header__ class -template with the same shared template parameters, and adds the `body` data -member. The message class template requires an additional template argument -type [*`Body`]. This type controls the container used to represent the body, -if any, as well as the algorithms needed to serialize and parse bodies of -that type. - -This illustration shows the declarations and members of the __header__ and -__message__ class templates, as well as the inheritance relationship: - -[$images/message.png [width 650px] [height 390px]] - -For notational convenience, these template type aliases are provided which -supply typical choices for the [*`Fields`] type: -``` -using request_header = header; -using response_header = header; - -template -using request = message; - -template -using response = message; -``` - -The code examples below show how to create and fill in a request and response -object: - -[table Create Message -[[HTTP Request] [HTTP Response]] -[[ - ``` - request req; - req.version = 11; // HTTP/1.1 - req.method = "GET"; - req.url = "/index.htm" - req.fields.insert("Accept", "text/html"); - req.fields.insert("Connection", "keep-alive"); - req.fields.insert("User-Agent", "Beast"); - ``` -][ - ``` - response res; - res.version = 11; // HTTP/1.1 - res.status = 200; - res.reason = "OK"; - res.fields.insert("Sever", "Beast"); - res.fields.insert("Content-Length", 4); - res.body = "****"; - ``` -]]] - -In the serialized format of a HTTP message, the header is represented as a -series of text lines ending in CRLF (`"\r\n"`). The end of the header is -indicated by a line containing only CRLF. Here are examples of serialized HTTP -request and response objects. The objects created above will produce these -results when serialized. Note that only the response has a body: - -[table Serialized HTTP Request and Response -[[HTTP Request] [HTTP Response]] -[[ - ``` - GET /index.htm HTTP/1.1\r\n - Accept: text/html\r\n - Connection: keep-alive\r\n - User-Agent: Beast\r\n - \r\n - ``` -][ - ``` - 200 OK HTTP/1.1\r\n - Server: Beast\r\n - Content-Length: 4\r\n - \r\n - **** - ``` -]]] - - - - -[endsect] - - - - -[section:fields Fields] - -The [*`Fields`] type represents a container that can set or retrieve the -fields in a message. Beast provides the -[link beast.ref.http__basic_fields `basic_fields`] class which serves -the needs for most users. It supports modification and inspection of values. -The field names are not case-sensitive. - -These statements change the values of the headers in the message passed: -``` - template - void set_fields(request& req) - { - if(! req.exists("User-Agent")) - req.insert("User-Agent", "myWebClient"); - - if(req.exists("Accept-Charset")) - req.erase("Accept-Charset"); - - req.replace("Accept", "text/plain"); - } -``` - -User defined [*`Fields`] types are possible. To support serialization, the -type must meet the requirements of __FieldSequence__. To support parsing using -the provided parser, the type must provide the `insert` member function. - -[endsect] - - - -[section:body Body] - -The message [*`Body`] template parameter controls both the type of the data -member of the resulting message object, and the algorithms used during parsing -and serialization. Beast provides three very common [*`Body`] types: - -* [link beast.ref.http__empty_body [*`empty_body`:]] An empty message body. -Used in GET requests where there is no message body. Example: -``` - request req; - req.version = 11; - req.method = "GET"; - req.url = "/index.html"; -``` - -* [link beast.ref.http__string_body [*`string_body`:]] A body with a -`value_type` as `std::string`. Useful for quickly putting together a request -or response with simple text in the message body (such as an error message). -Has the same insertion complexity of `std::string`. This is the type of body -used in the examples: -``` - response res; - static_assert(std::is_same::value); - res.body = "Here is the data you requested"; -``` - -* [link beast.ref.http__streambuf_body [*`streambuf_body`:]] A body with a -`value_type` of [link beast.ref.streambuf `streambuf`]: an efficient storage -object which uses multiple octet arrays of varying lengths to represent data. - -[heading Advanced] - -User-defined types are possible for the message body, where the type meets the -[link beast.ref.Body [*`Body`]] requirements. This simplified class declaration -shows the customization points available to user-defined body types: - -[$images/body.png [width 510px] [height 210px]] - -* [*`value_type`]: Determines the type of the - [link beast.ref.http__message.body `message::body`] member. If this - type defines default construction, move, copy, or swap, then message objects - declared with this [*`Body`] will have those operations defined. - -* [*`reader`]: An optional nested type meeting the requirements of - [link beast.ref.Reader [*`Reader`]]. If present, this defines the algorithm - used for parsing bodies of this type. - -* [*`writer`]: An optional nested type meeting the requirements of - [link beast.ref.Writer [*`Writer`]]. If present, this defines the algorithm - used for serializing bodies of this type. - -The examples included with this library provide a Body implementation that -serializing message bodies that come from a file. - -[endsect] - - - -[section:algorithms Algorithms] - -Algorithms are provided to serialize and deserialize HTTP/1 messages on -streams. - -* [link beast.ref.http__read [*read]]: Deserialize a HTTP/1 __header__ or __message__ from a stream. -* [link beast.ref.http__write [*write]]: Serialize a HTTP/1 __header__ or __message__ to a stream. - -Asynchronous versions of these algorithms are also available: - -* [link beast.ref.http__async_read [*async_read]]: Deserialize a HTTP/1 __header__ or __message__ asynchronously from a stream. -* [link beast.ref.http__async_write [*async_write]]: Serialize a HTTP/1 __header__ or __message__ asynchronously to a stream. - -[heading Using Sockets] - -The free function algorithms are modeled after Boost.Asio to send and receive -messages on TCP/IP sockets, SSL streams, or any object which meets the -Boost.Asio type requirements (__SyncReadStream__, __SyncWriteStream__, -__AsyncReadStream__, and __AsyncWriteStream__ depending on the types of -operations performed). To send messages synchronously, use one of the -[link beast.ref.http__write `write`] functions: -``` - void send_request(boost::asio::ip::tcp::socket& sock) - { - request req; - req.version = 11; - req.method = "GET"; - req.url = "/index.html"; - ... - write(sock, req); // Throws exception on error - ... - // Alternatively - boost::system::error:code ec; - write(sock, req, ec); - if(ec) - std::cerr << "error writing http message: " << ec.message(); - } -``` - -An asynchronous interface is available: -``` - void handle_write(boost::system::error_code); - ... - request req; - ... - async_write(sock, req, std::bind(&handle_write, std::placeholders::_1)); -``` - -When the implementation reads messages from a socket, it can read bytes lying -after the end of the message if they are present (the alternative is to read -a single byte at a time which is unsuitable for performance reasons). To -store and re-use these extra bytes on subsequent messages, the read interface -requires an additional parameter: a [link beast.ref.DynamicBuffer [*`DynamicBuffer`]] -object. This example reads a message from the socket, with the extra bytes -stored in the streambuf parameter for use in a subsequent call to read: -``` - boost::asio::streambuf sb; - ... - response res; - read(sock, sb, res); // Throws exception on error - ... - // Alternatively - boost::system::error:code ec; - read(sock, sb, res, ec); - if(ec) - std::cerr << "error reading http message: " << ec.message(); -``` - -As with the write function, an asynchronous interface is available. The -stream buffer parameter must remain valid until the completion handler is -called: -``` - void handle_read(boost::system::error_code); - ... - boost::asio::streambuf sb; - response res; - ... - async_read(sock, res, std::bind(&handle_read, std::placeholders::_1)); -``` - -An alternative to using a `boost::asio::streambuf` is to use a -__streambuf__, which meets the requirements of __DynamicBuffer__ and -is optimized for performance: -``` - void handle_read(boost::system::error_code); - ... - beast::streambuf sb; - response res; - read(sock, sb, res); -``` - -The `read` implementation can use any object meeting the requirements of -__DynamicBuffer__, allowing callers to define custom -memory management strategies used by the implementation. - -[endsect] - - - -[endsect] diff --git a/doc/images/AlfaSlabOne-Regular.ttf b/doc/images/AlfaSlabOne-Regular.ttf new file mode 100644 index 0000000000..ad70ebc75d Binary files /dev/null and b/doc/images/AlfaSlabOne-Regular.ttf differ diff --git a/doc/images/SIL Open Font License.txt b/doc/images/SIL Open Font License.txt new file mode 100644 index 0000000000..1cde0c6a7d --- /dev/null +++ b/doc/images/SIL Open Font License.txt @@ -0,0 +1,43 @@ +Copyright (c) 2011 JM Sole (info@jmsole.cl), with Reserved Font Name "Alfa Slab One" + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the copyright statement(s). + +"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. + +5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. \ No newline at end of file diff --git a/doc/images/body.png b/doc/images/body.png deleted file mode 100644 index 54d05550c3..0000000000 Binary files a/doc/images/body.png and /dev/null differ diff --git a/doc/images/body.psd b/doc/images/body.psd deleted file mode 100644 index 3d8dca09a5..0000000000 Binary files a/doc/images/body.psd and /dev/null differ diff --git a/doc/images/btc_qr2.png b/doc/images/btc_qr2.png new file mode 100644 index 0000000000..06380d6e99 Binary files /dev/null and b/doc/images/btc_qr2.png differ diff --git a/doc/images/message.png b/doc/images/message.png index 9b88298a16..b1675df8ca 100644 Binary files a/doc/images/message.png and b/doc/images/message.png differ diff --git a/doc/images/message.psd b/doc/images/message.psd index 3411c20c82..1f6ceaac53 100644 Binary files a/doc/images/message.psd and b/doc/images/message.psd differ diff --git a/doc/images/server.png b/doc/images/server.png new file mode 100644 index 0000000000..fc60980056 Binary files /dev/null and b/doc/images/server.png differ diff --git a/doc/images/server.psd b/doc/images/server.psd new file mode 100644 index 0000000000..cf9d445257 Binary files /dev/null and b/doc/images/server.psd differ diff --git a/doc/makeqbk.sh b/doc/makeqbk.sh index 07b2b69441..b742a28bf3 100755 --- a/doc/makeqbk.sh +++ b/doc/makeqbk.sh @@ -5,9 +5,12 @@ # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -mkdir -p ../bin/doc/xml +mkdir -p temp doxygen source.dox -cd ../bin/doc/xml +cd temp xsltproc combine.xslt index.xml > all.xml -cd ../../../doc -xsltproc reference.xsl ../bin/doc/xml/all.xml > reference.qbk +cp ../docca/include/docca/doxygen.xsl doxygen.xsl +sed -i -e '//{r ../xsl/class_detail.xsl' -e 'd}' doxygen.xsl +sed -i -e '//{r ../xsl/includes.xsl' -e 'd}' doxygen.xsl +sed -i -e '//{r ../xsl/includes_foot.xsl' -e 'd}' doxygen.xsl +xsltproc ../xsl/reference.xsl all.xml > ../reference.qbk diff --git a/doc/master.qbk b/doc/master.qbk deleted file mode 100644 index 24494c3886..0000000000 --- a/doc/master.qbk +++ /dev/null @@ -1,117 +0,0 @@ -[/ - 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) -] - -[library Beast - [quickbook 1.6] - [copyright 2013 - 2017 Vinnie Falco] - [purpose Networking Protocol Library] - [license - Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt or copy at - [@http://www.boost.org/LICENSE_1_0.txt]) - ] - [authors [Falco, Vinnie]] - [category template] - [category generic] -] - -[template mdash[] '''— '''] -[template indexterm1[term1] ''''''[term1]''''''] -[template indexterm2[term1 term2] ''''''[term1]''''''[term2]''''''] - -[def __N4588__ [@http://cplusplus.github.io/networking-ts/draft.pdf [*N4588]]] -[def __rfc6455__ [@https://tools.ietf.org/html/rfc6455 rfc6455]] -[def __rfc7230__ [@https://tools.ietf.org/html/rfc7230 rfc7230]] - -[def __asio_handler_invoke__ [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/asio_handler_invoke.html `asio_handler_invoke`]] -[def __asio_handler_allocate__ [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/asio_handler_allocate.html `asio_handler_allocate`]] -[def __void_or_deduced__ [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/asynchronous_operations.html#boost_asio.reference.asynchronous_operations.return_type_of_an_initiating_function ['void-or-deduced]]] - -[def __AsyncReadStream__ [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/AsyncReadStream.html [*AsyncReadStream]]] -[def __AsyncWriteStream__ [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/AsyncWriteStream.html [*AsyncWriteStream]]] -[def __CompletionHandler__ [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/CompletionHandler.html [*CompletionHandler]]] -[def __ConstBufferSequence__ [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/ConstBufferSequence.html [*ConstBufferSequence]]] -[def __Handler__ [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/Handler.html [*Handler]]] -[def __MutableBufferSequence__ [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/MutableBufferSequence.html [*MutableBufferSequence]]] -[def __SyncReadStream__ [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/SyncReadStream.html [*SyncReadStream]]] -[def __SyncWriteStream__ [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/SyncWriteStream.html [*SyncWriteStream]]] - -[def __Body__ [link beast.ref.Body [*`Body`]]] -[def __DynamicBuffer__ [link beast.ref.DynamicBuffer [*DynamicBuffer]]] -[def __FieldSequence__ [link beast.ref.FieldSequence [*FieldSequence]]] -[def __Parser__ [link beast.ref.Parser [*`Parser`]]] - -[def __basic_fields__ [link beast.ref.http__basic_fields `basic_fields`]] -[def __fields__ [link beast.ref.http__fields `fields`]] -[def __header__ [link beast.ref.http__header `header`]] -[def __message__ [link beast.ref.http__message `message`]] -[def __streambuf__ [link beast.ref.streambuf `streambuf`]] -[def __basic_streambuf__ [link beast.ref.basic_streambuf `basic_streambuf`]] - -Beast is a cross-platform, header-only C++ library built on Boost.Asio that -provides implementations of the HTTP and WebSocket protocols. - -[variablelist - [[ - [link beast.overview Overview] - ][ - An introduction with features, requirements, and credits. - ]] - [[ - [link beast.http Using HTTP] - ][ - How to use Beast's HTTP interfaces in your applications. - ]] - [[ - [link beast.websocket Using WebSocket] - ][ - How to use Beast's WebSocket interfaces in your applications. - ]] - [[ - [link beast.example Examples] - ][ - Examples that illustrate the use of Beast in more complex applications. - ]] - [[ - [link beast.design Design] - ][ - Design rationale, answers to review questions, and - other library comparisons. - ]] - [[ - [link beast.ref Reference] - ][ - Detailed class and function reference. - ]] - [[ - [link beast.index Index] - ][ - Book-style text index of Beast documentation. - ]] -] - -[include overview.qbk] -[include http.qbk] -[include websocket.qbk] -[include examples.qbk] -[include design.qbk] - -[section:ref Reference] -[xinclude quickref.xml] -[include types/Body.qbk] -[include types/BufferSequence.qbk] -[include types/DynamicBuffer.qbk] -[include types/Field.qbk] -[include types/FieldSequence.qbk] -[include types/Parser.qbk] -[include types/Reader.qbk] -[include types/Streams.qbk] -[include types/Writer.qbk] -[include reference.qbk] -[endsect] - -[xinclude index.xml] diff --git a/doc/overview.qbk b/doc/overview.qbk deleted file mode 100644 index 9f33aa73aa..0000000000 --- a/doc/overview.qbk +++ /dev/null @@ -1,114 +0,0 @@ -[/ - 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) -] - -[section:overview Introduction] - -Beast is a header-only, cross-platform C++ library built on Boost.Asio and -parts of Boost, containing two modules implementing widely used network -protocols. Beast offers a universal HTTP message model, plus algorithms for -parsing and serializing HTTP/1 messages. Beast.WebSocket provides a complete -implementation of the WebSocket protocol. Their design achieves these goals: - -* [*Symmetry.] Interfaces are role-agnostic; the same interfaces can be -used to build clients, servers, or both. - -* [*Ease of Use.] HTTP messages are modeled using simple, readily -accessible objects. Functions and classes used to send and receive HTTP -or WebSocket messages are designed to resemble Boost.Asio as closely as -possible. Users familiar with Boost.Asio will be immediately comfortable -using this library. - -* [*Flexibility.] Interfaces do not mandate specific implementation -strategies; important decisions such as buffer or thread management are -left to users of the library. - -* [*Performance.] The implementation performs competitively, making it a -realistic choice for building high performance network servers. - -* [*Scalability.] Development of network applications that scale to thousands -of concurrent connections is possible with the implementation. - -* [*Basis for further abstraction.] The interfaces facilitate the -development of other libraries that provide higher levels of abstraction. - -The HTTP portion of Beast is designed to be a low-level building block for -creating higher level libraries. It implements only the HTTP protocol, and -does not handle domain specific features (for example: cookies, redirects, or -deflate content encodings). - -[heading Requirements] - -Beast requires: - -* [*C++11.] A minimum of C++11 is needed. -* [*Boost.] Beast is built on Boost, especially Boost.Asio. -* [*OpenSSL.] If using TLS/Secure sockets (optional). - -[note Tested compilers: msvc-14+, gcc 5+, clang 3.6+] - -The library is [*header-only]. It is not necessary to add any .cpp files, -or to add commands to your build script for building Beast. To link your -program successfully, you'll need to add the Boost.System library to link -with. If you use coroutines you'll also need the Boost.Coroutine library. -Please visit the Boost documentation for instructions on how to do this for -your particular build system. - -[heading Motivation] - -Beast is built on Boost.Asio. A proposal to add networking functionality to the -C++ standard library, based on Boost.Asio, is under consideration by the -committee and on track for standardization. Since the final approved networking -interface for the C++ standard library will likely closely resemble the current -interface of Boost.Asio, the choice of Boost.Asio as the network transport -layer is prudent. - -The HTTP protocol is pervasive in network applications. As C++ is a logical -choice for high performance network servers, there is great utility in solid -building blocks for manipulating, sending, and receiving HTTP messages -compliant with the Hypertext Transfer Protocol and the supplements that -follow. Unfortunately reliable implementations or industry standards do not -exist in C++. The development of higher level libraries is stymied by the -lack of a common set of low-level algorithms and types for interacting with -the HTTP protocol. - -Today's web applications increasingly rely on alternatives to standard HTTP -to achieve performance and/or responsiveness. While WebSocket implementations -are widely available in common web development languages such as Javascript, -good implementations in C++ are scarce. A survey of existing C++ WebSocket -solutions reveals interfaces which lack symmetry, impose performance penalties, -and needlessly restrict implementation strategies. - -Beast.WebSocket takes advantage of Boost.Asio's extensible asynchronous -model, handler allocation, and handler invocation hooks. Calls to -Beast.WebSocket asynchronous initiation functions allow callers the choice -of using a completion handler, stackful or stackless coroutines, futures, -or user defined customizations (for example, Boost.Fiber). The -implementation uses handler invocation hooks (__asio_handler_invoke__), -providing execution guarantees on composed operations in a manner identical -to Boost.Asio. The implementation also uses handler allocation hooks -(__asio_handler_allocate__) when allocating memory internally for composed -operations. - -There is no need for inheritance or virtual members in a -[link beast.ref.websocket__stream `websocket::stream`]. -All operations are templated and transparent to the compiler, allowing for -maximum inlining and optimization. - -[heading Credits] - -Boost.Asio is the inspiration behind which all of the interfaces and -implementation strategies are built. Some parts of the documentation are -written to closely resemble the wording and presentation of Boost.Asio -documentation. Credit goes to Christopher Kohlhoff for the wonderful -Asio library and the ideas upon which Beast is built. - -Beast would not be possible without the considerable time and patience -contributed by David Schwartz, Edward Hennis, Howard Hinnant, Miguel Portilla, -Nikolaos Bougalis, Scott Determan, Scott Schurr, and Ripple Labs for -supporting its development. - -[endsect] diff --git a/doc/quickref.xml b/doc/quickref.xml index 874c5a6936..3a0f5961a8 100644 --- a/doc/quickref.xml +++ b/doc/quickref.xml @@ -29,115 +29,113 @@ Classes - basic_dynabuf_body - basic_fields - basic_parser_v1 - empty_body - fields - header - header_parser_v1 - message - parser_v1 - request - request_header - response - response_header - streambuf_body - string_body - - rfc7230 - - - ext_list - param_list - token_list + basic_dynamic_body + basic_file_body + basic_fields + basic_parser + basic_string_body + buffer_body + dynamic_body + empty_body + fields + header + message + parser + no_chunk_decorator + request + request_header + request_parser + request_serializer + response + response_header + response_parser + response_serializer + serializer + span_body + string_body + vector_body Functions - async_read - async_parse - async_write - chunk_encode - chunk_encode_final - swap - is_keep_alive - is_upgrade - operator<< - parse - prepare - read - reason_string - with_body - write + async_read + async_read_header + async_read_some + async_write + async_write_header + async_write_some + int_to_status + obsolete_reason + operator<< + read + read_header + read_some + string_to_field + string_to_verb + swap + to_string + to_status_class + write + write_header + write_some - Type Traits + rfc7230 - is_Body - is_Parser - is_Reader - is_Writer - has_reader - has_writer + ext_list + opt_token_list + param_list + token_list - Options - - header_max_size - body_max_size - skip_body - Constants - body_what - connection - no_content_length - parse_error - parse_flag + error + field + status + status_class + verb + + Type Traits + + is_body + is_body_writer + is_body_reader + is_fields Concepts - Body - Field - FieldSequence - Parser - Reader - Writer + Body + BodyReader + BodyWriter + Fields + FieldsReader Classes - close_reason - ping_data - stream - reason_string - teardown_tag + close_reason + ping_data + stream + reason_string Functions - async_teardown - teardown + async_teardown + is_upgrade + teardown Options - auto_fragment - decorate - keep_alive - message_type - permessage_deflate - ping_callback - read_buffer_size - read_message_max - write_buffer_size + permessage_deflate Constants - close_code - error - opcode + close_code + error + frame_type @@ -151,10 +149,10 @@ - + Core - + ZLib @@ -164,78 +162,112 @@ Classes - async_completion - basic_streambuf - buffers_adapter - consuming_buffers - dynabuf_readstream - errc - error_category - error_code - error_condition - handler_alloc - handler_ptr - static_streambuf - static_streambuf_n - static_string - streambuf - system_error + async_completion + async_result + async_return_type + basic_flat_buffer + basic_multi_buffer + buffer_cat_view + buffer_prefix_view + buffered_read_stream + buffers_adapter + consuming_buffers + drain_buffer + error_category + error_code + error_condition + file + file_mode + + + +   + + file_posix + file_stdio + file_win32 + flat_buffer + handler_alloc + handler_ptr + handler_type + iequal + iless + multi_buffer + span + static_buffer + static_buffer_n + static_string + string_param + string_view + system_error Functions - bind_handler - buffer_cat - prepare_buffer - prepare_buffers - system_category - to_string - write + bind_handler + buffer_cat + buffer_prefix + buffers + generic_category + iequals + ostream + read_size + read_size_or_throw + system_category + to_static_string + + Constants + + errc + file_mode Type Traits - is_AsyncReadStream - is_AsyncWriteStream - is_AsyncStream - is_BufferSequence - is_CompletionHandler - is_ConstBufferSequence - is_DynamicBuffer - is_MutableBufferSequence - is_SyncReadStream - is_SyncStream - is_SyncWriteStream + get_lowest_layer + has_get_io_service + is_async_read_stream + is_async_write_stream + is_async_stream + is_completion_handler + is_const_buffer_sequence + is_dynamic_buffer + is_file + is_mutable_buffer_sequence + is_sync_read_stream + is_sync_stream + is_sync_write_stream Concepts - AsyncStream - BufferSequence - DynamicBuffer - Stream - SyncStream + AsyncStream + BufferSequence + DynamicBuffer + File + Stream + SyncStream Classes - deflate_stream - inflate_stream - z_params + deflate_stream + inflate_stream + z_params Functions - deflate_upper_bound + deflate_upper_bound Constants - error - Flush - Strategy + error + Flush + Strategy @@ -254,8 +286,8 @@ - doc_debug - nested_doc_debug + doc_debug + nested_doc_debug diff --git a/doc/source.dox b/doc/source.dox index 624ab63310..7ead3bb099 100644 --- a/doc/source.dox +++ b/doc/source.dox @@ -16,7 +16,7 @@ ABBREVIATE_BRIEF = ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = YES FULL_PATH_NAMES = NO -STRIP_FROM_PATH = +STRIP_FROM_PATH = ../include/ STRIP_FROM_INC_PATH = SHORT_NAMES = NO JAVADOC_AUTOBRIEF = YES @@ -254,7 +254,7 @@ MAN_LINKS = NO # Configuration options related to the XML output #--------------------------------------------------------------------------- GENERATE_XML = YES -XML_OUTPUT = ../bin/doc/xml +XML_OUTPUT = temp/ XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- @@ -282,8 +282,12 @@ EXPAND_ONLY_PREDEF = YES SEARCH_INCLUDES = YES INCLUDE_PATH = ../ INCLUDE_FILE_PATTERNS = -PREDEFINED = DOXYGEN \ - GENERATING_DOCS + +PREDEFINED = \ + BEAST_DOXYGEN \ + BEAST_USE_POSIX_FILE=1 \ + BEAST_USE_WIN32_FILE=1 + EXPAND_AS_DEFINED = SKIP_FUNCTION_MACROS = YES diff --git a/doc/types/Body.qbk b/doc/types/Body.qbk deleted file mode 100644 index b539fae655..0000000000 --- a/doc/types/Body.qbk +++ /dev/null @@ -1,49 +0,0 @@ -[/ - 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) -] - -[section:Body Body requirements] - -A [*Body] type is supplied as a template argument to the __message__ class. It -controls both the type of the data member of the resulting message object, and -the algorithms used during parsing and serialization. - -In this table: - -* `X` is a type meeting the requirements of [*`Body`]. - -[table Body requirements -[[operation] [type] [semantics, pre/post-conditions]] -[ - [`X::value_type`] - [] - [ - The type of the `message::body` member. - If this is not movable or not copyable, the containing message - will be not movable or not copyable. - ] -] -[ - [`X::reader`] - [] - [ - If present, a type meeting the requirements of - [link beast.ref.Reader [*`Reader`]]. - Provides an implementation to parse the body. - ] -] -[ - [`X::writer`] - [] - [ - If present, a type meeting the requirements of - [link beast.ref.Writer [*`Writer`]]. - Provides an implementation to serialize the body. - ] -] -] - -[endsect] diff --git a/doc/types/BufferSequence.qbk b/doc/types/BufferSequence.qbk deleted file mode 100644 index c008457be9..0000000000 --- a/doc/types/BufferSequence.qbk +++ /dev/null @@ -1,15 +0,0 @@ -[/ - 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) -] - -[section:BufferSequence BufferSequence requirements] - -A `BufferSequence` is a type meeting either of the following requirements: - -* [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/ConstBufferSequence.html [*`ConstBufferSequence`]] -* [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/MutableBufferSequence.html [*`MutableBufferSequence`]] - -[endsect] diff --git a/doc/types/Field.qbk b/doc/types/Field.qbk deleted file mode 100644 index e628b980b8..0000000000 --- a/doc/types/Field.qbk +++ /dev/null @@ -1,41 +0,0 @@ -[/ - 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) -] - -[section:Field Field requirements] - -A [*`Field`] represents a single HTTP header field/value pair. - -In this table: - -* `X` denotes a type meeting the requirements of [*`Field`]. -* `a` denotes a value of type `X`. - -[table Field requirements - -[[operation][type][semantics, pre/post-conditions]] -[ - [`a.name()`] - [`boost::string_ref`] - [ - This function returns a value implicitly convertible to - `boost::string_ref` containing the case-insensitive field - name, without leading or trailing white space. - ] -] -[ - [`a.value()`] - [`boost::string_ref`] - [ - This function returns a value implicitly convertible to - `boost::string_ref` containing the value for the field. The - value is considered canonical if there is no leading or - trailing whitespace. - ] -] -] - -[endsect] diff --git a/doc/types/FieldSequence.qbk b/doc/types/FieldSequence.qbk deleted file mode 100644 index 2c3aa7f1ad..0000000000 --- a/doc/types/FieldSequence.qbk +++ /dev/null @@ -1,60 +0,0 @@ -[/ - 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) -] - -[section:FieldSequence FieldSequence requirements] - -A [*FieldSequence] is an iterable container whose value type meets -the requirements of [link beast.ref.Field [*Field]]. Objects that meet -these requirements become serializable by the implementation. - -In this table: - -* `X` denotes a type that meets the requirements of [*FieldSequence]. - -* `c` is a value of type `X const`. - -[table FieldSequence requirements -[[operation][type][semantics, pre/post-conditions]] -[ - [`X::value_type`] - [] - [ - A type that meets the requirements of [link beast.ref.Field [*Field]]. - ] -] -[ - [`X::const_iterator`] - [] - [ - An iterator type whose `reference` type meets the - requirements of [link beast.ref.Field [*Field]], and which - satisfies all the requirements of [*ForwardIterator], - except that: - - [ordered_list - [there is no requirement that `operator->` is provided, and] - [there is no requirement that `reference` be a reference type.] - ] - ] -] -[ - [`c.begin()`] - [`X::const_iterator`] - [ - Returns an iterator to the beginning of the field sequence. - ] -] -[ - [`c.end()`] - [`X::const_iterator`] - [ - Returns an iterator to the end of the field sequence. - ] -] -] - -[endsect] diff --git a/doc/types/Parser.qbk b/doc/types/Parser.qbk deleted file mode 100644 index 12cd7b749b..0000000000 --- a/doc/types/Parser.qbk +++ /dev/null @@ -1,61 +0,0 @@ -[/ - 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) -] - -[section:Parser Parser requirements] - -A [*Parser] is used to deserialize objects from -[link beast.ref.streams streams]. Objects of this type are used with -[link beast.ref.http__parse http::parse] and -[link beast.ref.http__async_parse http::async_parse]. The definition of -an object, and the predicate defining when the parse is complete, are -determined by the implementation. - -In this table: - -* `X` denotes a type meeting the requirements of [*Parser]. - -* `a` denotes a value of type `X`. - -* `b` is a value meeting the requirements of __ConstBufferSequence__. - -* `ec` is a value of type [link beast.ref.error_code `error_code&`]. - -[table Parser requirements -[[operation] [type] [semantics, pre/post-conditions]] -[ - [`a.complete()`] - [`bool`] - [ - Returns `true` when parsing is complete. - ] -] -[ - [`a.write(b, ec)`] - [`std::size_t`] - [ - Sequentially parses the octets in the specified input buffer sequence - until an error occurs, the end of the buffer is reached, or parsing is - complete. Upon success, this function returns the number of bytes used - from the input. If an error occurs, `ec` is set to the error code and - parsing stops. - ] -] -[ - [`a.write_eof(ec)`] - [`void`] - [ - Indicates to the parser that no more octets will be available. - Typically this function is called when the end of stream is reached. - For example, if a call to `boost::asio::ip::tcp::socket::read_some` - generates a `boost::asio::error::eof` error. Some objects, such as - certain HTTP/1 messages, determine the end of the message body by - an end of file marker or closing of the connection. - ] -] -] - -[endsect] diff --git a/doc/types/Reader.qbk b/doc/types/Reader.qbk deleted file mode 100644 index 0fd852f1c9..0000000000 --- a/doc/types/Reader.qbk +++ /dev/null @@ -1,69 +0,0 @@ -[/ - 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) -] - -[section:Reader Reader requirements] - -Parsers provided by the implementation will construct the corresponding -`reader` object during parsing. This customization point allows the -Body to determine the strategy for storing incoming message body data. - -In this table: - -* `X` denotes a type meeting the requirements of [*`Reader`]. - -* `a` denotes a value of type `X`. - -* `n` is a value convertible to `std::size_t`. - -* `p` is a `void const*` to valid memory of at least `n` bytes. - -* `ec` is a value of type [link beast.ref.error_code `error_code&`]. - -* `m` denotes a value of type `message&` where - `std::is_same::value == true`. - -[table Reader requirements -[[operation] [type] [semantics, pre/post-conditions]] -[ - [`X a(m);`] - [] - [ - `a` is constructible from `m`. The lifetime of `m` is guaranteed - to end no earlier than after `a` is destroyed. The constructor - will be called after all headers have been stored in `m`, and - before any body data is deserialized. This function must be - `noexcept`. - ] -] -[ - [`a.init(ec)`] - [`void`] - [ - Called immediately after construction. If the function sets - an error code in `ec`, the parse is aborted and the error is - propagated to the caller. This function must be `noexcept`. - ] -] -[ - [`a.write(p, n, ec)`] - [`void`] - [ - Deserializes the input sequence into the body. If `ec` is set, - the deserialization is aborted and the error is propagated to - the caller. If the message headers specify a chunked transfer - encoding, the reader will receive the decoded version of the - body. This function must be `noexcept`. - ] -] -] - -[note - Definitions for required `Reader` member functions should be declared - inline so the generated code can become part of the implementation. -] - -[endsect] diff --git a/doc/types/Streams.qbk b/doc/types/Streams.qbk deleted file mode 100644 index 67d6037550..0000000000 --- a/doc/types/Streams.qbk +++ /dev/null @@ -1,34 +0,0 @@ -[/ - 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) -] - -[section:streams Streams requirements] - -Stream types represent objects capable of performing synchronous or -asynchronous I/O. They are based on concepts from `boost::asio`. - -[heading:Stream Stream] - -A type modeling [*`Stream`] meets either or both of the following requirements: - -* [*`AsyncStream`] -* [*`SyncStream`] - -[heading:AsyncStream AsyncStream] - -A type modeling [*`AsyncStream`] meets the following requirements: - -* [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/AsyncReadStream.html [*`AsyncReadStream`]] -* [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/AsyncWriteStream.html [*`AsyncWriteStream`]] - -[heading:SyncStream SyncStream] - -A type modeling [*`SyncStream`] meets the following requirements: - -* [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/SyncReadStream.html [*`SyncReadStream`]] -* [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/SyncWriteStream.html [*`SyncWriteStream`]] - -[endsect] diff --git a/doc/types/Writer.qbk b/doc/types/Writer.qbk deleted file mode 100644 index aef4601ce4..0000000000 --- a/doc/types/Writer.qbk +++ /dev/null @@ -1,161 +0,0 @@ -[/ - 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) -] - -[section:Writer Writer requirements] - -A `Writer` serializes the message body. The implementation creates an instance -of this type when serializing a message, and calls into it zero or more times -to provide buffers containing the data. The interface of `Writer` is intended -to allow serialization in these scenarios: - -* A body that does not entirely fit in memory. -* A body produced incrementally from coroutine output. -* A body represented by zero or more buffers already in memory. -* A body as a series of buffers when the content size is not known ahead of time. -* Body data generated on demand from other threads. -* Body data computed algorithmically. - -In this table: - -* `X` denotes a type meeting the requirements of `Writer`. - -* `a` denotes a value of type `X`. - -* `m` denotes a value of type `message const&` where - `std::is_same:value == true`. - -* `ec` is a value of type [link beast.ref.error_code `error_code&`] - -* `wf` is a [*write function]: a function object of unspecified type provided - by the implementation which accepts any value meeting the requirements - of __ConstBufferSequence__ as its single parameter. - -[table Writer requirements -[[operation] [type] [semantics, pre/post-conditions]] -[ - [`X a(m);`] - [] - [ - `a` is constructible from `m`. The lifetime of `m` is guaranteed - to end no earlier than after `a` is destroyed. This function must - be `noexcept`. - ] -] -[ - [`a.init(ec)`] - [`void`] - [ - Called immediately after construction. If the function sets an - error code in `ec`, the serialization is aborted and the error - is propagated to the caller. This function must be `noexcept`. - ] -] -[ - [`a.content_length()`] - [`std::uint64_t`] - [ - If this member is present, it is called after initialization - and before calls to provide buffers. The serialized message will - have the Content-Length field set to the value returned from - this function. If this member is absent, the serialized message - body will be chunk-encoded for HTTP versions 1.1 and later, else - the serialized message body will be sent unmodified, with the - error `boost::asio::error::eof` returned to the caller, to notify - they should close the connection to indicate the end of the message. - This function must be `noexcept`. - ] -] -[ - [`a.write(ec, wf)`] - [`bool`] - [ - Called repeatedly after `init` succeeds. `wf` is a function object - which takes as its single parameter any value meeting the requirements - of __ConstBufferSequence__. Buffers provided to this write function - must remain valid until the next member function of `writer` is - invoked (which may be the destructor). This function returns `true` - to indicate all message body data has been written, or `false` if - there is more body data. - ] -] -] - -[note - Definitions for required `Writer` member functions should be declared - inline so the generated code can become part of the implementation. -] - -Exemplar: -``` -struct writer -{ -public: - /** Construct the writer. - - The msg object is guaranteed to exist for the lifetime of the writer. - - Exceptions: - No-throw guarantee. - - @param msg The message whose body is to be written. - */ - template - explicit - writer(message const& msg) noexcept; - - /** Initialize the writer. - - Called once immediately after construction. - The writer can perform initialization which may fail. - - @param ec Contains the error code if any errors occur. - */ - void - init(error_code& ec) noexcept; - - /** Returns the content length. - - If this member is present, the implementation will set the - Content-Length field accordingly. If absent, the implementation will - use chunk-encoding or terminate the connection to indicate the end - of the message. - */ - std::uint64_t - content_length() noexcept; - - /** Write zero or one buffer representing the message body. - - Postconditions: - - If return value is `true`: - * Callee made zero or one calls to `write`. - * There is no more data remaining to write. - - If return value is `false`: - * Callee does not take ownership of resume. - * Callee made one call to `write`. - - @param ec Set to indicate an error. This will cause an - asynchronous write operation to complete with the error. - - @param write A functor the writer will call to provide the next - set of buffers. Ownership of the buffers is not transferred, - the writer must guarantee that the buffers remain valid until the - next member function is invoked, which may be the destructor. - - @return `true` if there is no more data to send, - `false` when there may be more data. - */ - template - bool - write( - error_code&, - WriteFunction&& wf) noexcept; -}; -``` - -[endsect] diff --git a/doc/websocket.qbk b/doc/websocket.qbk deleted file mode 100644 index 77bcc2bde0..0000000000 --- a/doc/websocket.qbk +++ /dev/null @@ -1,483 +0,0 @@ -[/ - 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) -] - -[section:websocket WebSocket] - -[block ''' - - Creation - Making connections - Handshaking - Messages - Frames - Control Frames - Buffers - Asynchronous interface - The io_service - Thread Safety - -'''] - -The WebSocket Protocol enables two-way communication between a client -running untrusted code in a controlled environment to a remote host that has -opted-in to communications from that code. The protocol consists of an opening -handshake followed by basic message framing, layered over TCP. The goal of -this technology is to provide a mechanism for browser-based applications that -need two-way communication with servers that does not rely on opening multiple -HTTP connections. - -Beast.WebSocket provides developers with a robust WebSocket implementation -built on Boost.Asio with a consistent asynchronous model using a modern -C++ approach. - -The WebSocket protocol is described fully in -[@https://tools.ietf.org/html/rfc6455 rfc6455] - -[note - The following documentation assumes familiarity with both - Boost.Asio and the WebSocket protocol specification described in __rfc6455__. -] - - - - -[section:creation Creation] - -The interface to Beast's WebSocket implementation is a single template -class [link beast.ref.websocket__stream `beast::websocket::stream`] which -wraps a "next layer" object. The next layer object must meet the requirements -of [link beast.ref.streams.SyncStream [*`SyncReadStream`]] if synchronous -operations are performed, or -[link beast.ref.streams.AsyncStream [*`AsyncStream`]] if asynchronous -operations are performed, or both. Arguments supplied during construction are -passed to next layer's constructor. Here we declare a websocket stream over -a TCP/IP socket with ownership of the socket: -``` -boost::asio::io_service ios; -beast::websocket::stream ws{ios}; -``` - -[heading Using SSL] - -To use WebSockets over SSL, choose an SSL stream for the next layer template -argument when constructing the stream. -``` -#include -#include -#include - -boost::asio::io_service ios; -boost::asio::ssl::context ctx{boost::asio::ssl::context::sslv23}; -beast::websocket::stream ws{ios, ctx}; -``` - -[note - When creating websocket stream objects using SSL, it is necessary - to include the file ``. -] - -[heading Non-owning references] - -For servers that can handshake in multiple protocols, it may be desired -to wrap an object that already exists. This socket can be moved in: -``` - boost::asio::ip::tcp::socket&& sock; - ... - beast::websocket::stream ws{std::move(sock)}; -``` - -Or, the wrapper can be constructed with a non-owning reference. In -this case, the caller is responsible for managing the lifetime of the -underlying socket being wrapped: -``` - boost::asio::ip::tcp::socket sock; - ... - beast::websocket::stream ws{sock}; -``` - -The layer being wrapped can be accessed through the websocket's "next layer", -permitting callers to interact directly with its interface. -``` - boost::asio::ssl::context ctx{boost::asio::ssl::context::sslv23}; - beast::websocket::stream> ws{ios, ctx}; - ... - ws.next_layer().shutdown(); // ssl::stream shutdown -``` - -[warning - Initiating read and write operations on the next layer while - stream operations are being performed can break invariants, and - result in undefined behavior. -] - -[endsect] - - - -[section:connections Making connections] - -Connections are established by using the interfaces which already exist -for the next layer. For example, making an outgoing connection: -``` - std::string const host = "mywebapp.com"; - boost::asio::io_service ios; - boost::asio::ip::tcp::resolver r{ios}; - beast::websocket::stream ws{ios}; - boost::asio::connect(ws.next_layer(), - r.resolve(boost::asio::ip::tcp::resolver::query{host, "ws"})); -``` - -Accepting an incoming connection: -``` -void do_accept(boost::asio::ip::tcp::acceptor& acceptor) -{ - beast::websocket::stream ws{acceptor.get_io_service()}; - acceptor.accept(ws.next_layer()); -} -``` - -[note - Examples use synchronous interfaces for clarity of exposition. -] - -[endsect] - - - -[section:handshaking Handshaking] - -A WebSocket session begins when one side sends the HTTP Upgrade request -for websocket, and the other side sends an appropriate HTTP response -indicating that the request was accepted and that the connection has -been upgraded. The HTTP Upgrade request must include the Host HTTP field, -and the URI of the resource to request. -[link beast.ref.websocket__stream.handshake `handshake`] is used to send the -request with the required host and resource strings. -``` - beast::websocket::stream ws{ios}; - ... - ws.set_option(beast::websocket::keep_alive(true)); - ws.handshake("ws.example.com:80", "/cgi-bin/bitcoin-prices"); -``` - -The [link beast.ref.websocket__stream `stream`] automatically -handles receiving and processing the HTTP response to the handshake request. -The call to handshake is successful if a HTTP response is received with the -101 "Switching Protocols" status code. On failure, an error is returned or an -exception is thrown. Depending on the keep alive setting, the socket may remain -open for a subsequent handshake attempt - -Performing a handshake for an incoming websocket upgrade request operates -similarly. If the handshake fails, an error is returned or exception thrown: -``` - beast::websocket::stream ws{ios}; - ... - ws.accept(); -``` - -Servers that can handshake in multiple protocols may have already read data -on the connection, or might have already received an entire HTTP request -containing the upgrade request. Overloads of `accept` allow callers to -pass in this additional buffered handshake data. -``` -void do_accept(boost::asio::ip::tcp::socket& sock) -{ - boost::asio::streambuf sb; - boost::asio::read_until(sock, sb, "\r\n\r\n"); - ... - beast::websocket::stream ws{sock}; - ws.accept(sb.data()); - ... -} -``` - -Alternatively, the caller can pass an entire HTTP request if it was -obtained elsewhere: -``` -void do_accept(boost::asio::ip::tcp::socket& sock) -{ - boost::asio::streambuf sb; - beast::http::request request; - beast::http::read(sock, sb, request); - if(beast::http::is_upgrade(request)) - { - websocket::stream ws{sock}; - ws.accept(request); - ... - } -} -``` - -[endsect] - - - -[section:messages Messages] - -After the WebSocket handshake is accomplished, callers may send and receive -messages using the message oriented interface. This interface requires that -all of the buffers representing the message are known ahead of time: -``` -void echo(beast::websocket::stream& ws) -{ - beast::streambuf sb; - beast::websocket::opcode::value op; - ws.read(op, sb); - - ws.set_option(beast::websocket::message_type{op}); - ws.write(sb.data()); - sb.consume(sb.size()); -} -``` - -[important - Calls to [link beast.ref.websocket__stream.set_option `set_option`] - must be made from the same implicit or explicit strand as that used - to perform other operations. -] - -[endsect] - - - -[section:frames Frames] - -Some use-cases make it impractical or impossible to buffer the entire -message ahead of time: - -* Streaming multimedia to an endpoint. -* Sending a message that does not fit in memory at once. -* Providing incremental results as they become available. - -For these cases, the frame oriented interface may be used. This -example reads and echoes a complete message using this interface: -``` -void echo(beast::websocket::stream& ws) -{ - beast::streambuf sb; - beast::websocket::frame_info fi; - for(;;) - { - ws.read_frame(fi, sb); - if(fi.fin) - break; - } - ws.set_option(beast::websocket::message_type{fi.op}); - beast::consuming_buffers< - beast::streambuf::const_buffers_type> cb{sb.data()}; - for(;;) - { - using boost::asio::buffer_size; - std::size_t size = std::min(buffer_size(cb)); - if(size > 512) - { - ws.write_frame(false, beast::prepare_buffers(512, cb)); - cb.consume(512); - } - else - { - ws.write_frame(true, cb); - break; - } - } -} -``` - -[endsect] - - - -[section:control Control Frames] - -Control frames are small (less than 128 bytes) messages entirely contained -in an individual WebSocket frame. They may be sent at any time by either -peer on an established connection, and can appear in between continuation -frames for a message. There are three types of control frames: ping, pong, -and close. - -A sent ping indicates a request that the sender wants to receive a pong. A -pong is a response to a ping. Pongs may be sent unsolicited, at any time. -One use for an unsolicited pong is to inform the remote peer that the -session is still active after a long period of inactivity. A close frame -indicates that the remote peer wishes to close the WebSocket connection. -The connection is considered gracefully closed when each side has sent -and received a close frame. - -During read operations, Beast automatically reads and processes control -frames. Pings are replied to as soon as possible with a pong, received -ping and pongs are delivered to the ping callback. The receipt of a close -frame initiates the WebSocket close procedure, eventually resulting in the -error code [link beast.ref.websocket__error `error::closed`] being delivered -to the caller in a subsequent read operation, assuming no other error -takes place. - -A consequence of this automatic behavior is that caller-initiated read -operations can cause socket writes. However, these writes will not -compete with caller-initiated write operations. For the purposes of -correctness with respect to the stream invariants, caller-initiated -read operations still only count as a read. This means that callers can -have a simultaneously active read, write, and ping operation in progress, -while the implementation also automatically handles control frames. - -[heading Ping and Pong Frames] - -Ping and pong messages are control frames which may be sent at any time -by either peer on an established WebSocket connection. They are sent -using the functions - [link beast.ref.websocket__stream.ping `ping`] and - [link beast.ref.websocket__stream.pong `pong`]. - -To be notified of ping and pong control frames, callers may register a -"ping callback" using [link beast.ref.websocket__stream.set_option `set_option`]. -The object provided with this option should be callable with the following -signature: -``` - void on_ping(bool is_pong, ping_data const& payload); - ... - ws.set_option(ping_callback{&on_ping}); -``` - -When a ping callback is registered, all pings and pongs received through -either synchronous read functions or asynchronous read functions will -invoke the ping callback, with the value of `is_pong` set to `true` if a -pong was received else `false` if a ping was received. The payload of -the ping or pong control frame is passed in the payload argument. - -Unlike regular completion handlers used in calls to asynchronous initiation -functions, the ping callback only needs to be set once. The callback is not -reset when a ping or pong is received. The same callback is used for both -synchronous and asynchronous reads. The ping callback is passive; in order -to receive pings and pongs, a synchronous or asynchronous stream read -function must be active. - -[note - When an asynchronous read function receives a ping or pong, the - ping callback is invoked in the same manner as that used to invoke - the final completion handler of the corresponding read function. -] - -[heading Close Frames] - -The WebSocket protocol defines a procedure and control message for initiating -a close of the session. Handling of close initiated by the remote end of the -connection is performed automatically. To manually initiate a close, use -the [link beast.ref.websocket__stream.close `close`] function: -``` - ws.close(); -``` - -When the remote peer initiates a close by sending a close frame, Beast -will handle it for you by causing the next read to return `error::closed`. -When this error code is delivered, it indicates to the application that -the WebSocket connection has been closed cleanly, and that the TCP/IP -connection has been closed. After initiating a close, it is necessary to -continue reading messages until receiving the error `error::closed`. This -is because the remote peer may still be sending message and control frames -before it receives and responds to the close frame. - -[important - To receive the [link beast.ref.websocket__error `error::closed`] - error, a read operation is required. -] - -[heading Auto-fragment] - -To ensure timely delivery of control frames, large messages can be broken up -into smaller sized frames. The automatic fragment option turns on this -feature, and the write buffer size option determines the maximum size of -the fragments: -``` - ... - ws.set_option(beast::websocket::auto_fragment{true}); - ws.set_option(beast::websocket::write_buffer_size{16384}); -``` - -[endsect] - - - -[section:buffers Buffers] - -Because calls to read data may return a variable amount of bytes, the -interface to calls that read data require an object that meets the requirements -of [link beast.ref.DynamicBuffer [*`DynamicBuffer`]]. This concept is modeled on -[@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/basic_streambuf.html `boost::asio::basic_streambuf`]. - -The implementation does not perform queueing or buffering of messages. If -desired, these features should be provided by callers. The impact of this -design is that library users are in full control of the allocation strategy -used to store data and the back-pressure applied on the read and write side -of the underlying TCP/IP connection. - -[endsect] - - - -[section:async Asynchronous interface] - -Asynchronous versions are available for all functions: -``` -websocket::opcode op; -ws.async_read(op, sb, - [](boost::system::error_code const& ec) - { - ... - }); -``` - -Calls to asynchronous initiation functions support the extensible asynchronous -model developed by the Boost.Asio author, allowing for traditional completion -handlers, stackful or stackless coroutines, and even futures: -``` -void echo(websocket::stream& ws, - boost::asio::yield_context yield) -{ - ws.async_read(sb, yield); - std::future fut = - ws.async_write, sb.data(), boost::use_future); - ... -} -``` - -[endsect] - - - -[section:io_service The io_service] - -The creation and operation of the -[@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/io_service.html `boost::asio::io_service`] -associated with the underlying stream is left to the callers, permitting any -implementation strategy including one that does not require threads for -environments where threads are unavailable. Beast.WebSocket itself does not -use or require threads. - -[endsect] - - - -[section:threads Thread Safety] - -Like a regular asio socket, a [link beast.ref.websocket__stream `stream`] is -not thread safe. Callers are responsible for synchronizing operations on the -socket using an implicit or explicit strand, as per the Asio documentation. -The asynchronous interface supports one active read and one active write -simultaneously. Undefined behavior results if two or more reads or two or -more writes are attempted concurrently. Caller initiated WebSocket ping, pong, -and close operations each count as an active write. - -The implementation uses composed asynchronous operations internally; a high -level read can cause both reads and writes to take place on the underlying -stream. This behavior is transparent to callers. - -[endsect] - - - -[endsect] - -[include quickref.xml] diff --git a/doc/xsl/class_detail.xsl b/doc/xsl/class_detail.xsl new file mode 100644 index 0000000000..1120891e7f --- /dev/null +++ b/doc/xsl/class_detail.xsl @@ -0,0 +1,51 @@ + + + class ``[link beast.concept.streams.AsyncStream [*AsyncStream]]`` + + + class __AsyncReadStream__ + + + class __AsyncWriteStream__ + + + class ``[link beast.concept.Body [*Body]]`` + + + class ``[link beast.concept.BufferSequence [*BufferSequence]]`` + + + + ``[link beast.concept.BufferSequence [*BufferSequence]]`` + + + class __CompletionHandler__ + + + class __ConstBufferSequence__ + + + class ``[link beast.concept.DynamicBuffer [*DynamicBuffer]]`` + + + class ``[link beast.concept.Fields [*Fields]]`` + + + class __Handler__ + + + class __MutableBufferSequence__ + + + class ``[link beast.concept.streams.Stream [*Stream]]`` + + + class ``[link beast.concept.streams.SyncStream [*SyncStream]]`` + + + class __SyncReadStream__ + + + class __SyncWriteStream__ + + diff --git a/doc/xsl/includes.xsl b/doc/xsl/includes.xsl new file mode 100644 index 0000000000..6f48e5fe5b --- /dev/null +++ b/doc/xsl/includes.xsl @@ -0,0 +1,5 @@ + + Defined in header [include_file + + ] + diff --git a/doc/xsl/includes_foot.xsl b/doc/xsl/includes_foot.xsl new file mode 100644 index 0000000000..ba723c06d8 --- /dev/null +++ b/doc/xsl/includes_foot.xsl @@ -0,0 +1,16 @@ + + + + Convenience header [include_file beast/core.hpp] + + + Convenience header [include_file beast/http.hpp] + + + Convenience header [include_file beast/websocket.hpp] + + + Convenience header [include_file beast/zlib.hpp] + + + diff --git a/doc/xsl/reference.xsl b/doc/xsl/reference.xsl new file mode 100644 index 0000000000..90a7c0e870 --- /dev/null +++ b/doc/xsl/reference.xsl @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 0000000000..cfaad504ba --- /dev/null +++ b/example/CMakeLists.txt @@ -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() diff --git a/example/Jamfile b/example/Jamfile new file mode 100644 index 0000000000..61c87b8f46 --- /dev/null +++ b/example/Jamfile @@ -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 ; diff --git a/example/common/detect_ssl.hpp b/example/common/detect_ssl.hpp new file mode 100644 index 0000000000..298a644baa --- /dev/null +++ b/example/common/detect_ssl.hpp @@ -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 +#include + +//------------------------------------------------------------------------------ +// +// Example: Detect TLS/SSL +// +//------------------------------------------------------------------------------ + +//[example_core_detect_ssl_1 + +#include +#include + +/** 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 +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::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::value, + "SyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::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 + composed operation. 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::value, + "SyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::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>{ + 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 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 + detect_ssl_op(AsyncReadStream& stream, + DynamicBuffer& buffer, DeducedHandler&& handler) + : stream_(stream) + , buffer_(buffer) + , handler_(std::forward(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 + 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:: +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 diff --git a/example/common/helpers.hpp b/example/common/helpers.hpp new file mode 100644 index 0000000000..784adc567e --- /dev/null +++ b/example/common/helpers.hpp @@ -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 +#include +#include +#include + +/// 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 +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 +void +print(std::ostream& os, Args const&... args) +{ + std::stringstream ss; + detail::print_1(ss, args...); + os << ss.str() << std::endl; +} + +#endif diff --git a/example/common/mime_types.hpp b/example/common/mime_types.hpp new file mode 100644 index 0000000000..65f646f983 --- /dev/null +++ b/example/common/mime_types.hpp @@ -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 +#include + +// Return a reasonable mime type based on the extension of a file. +// +template +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 diff --git a/example/common/rfc7231.hpp b/example/common/rfc7231.hpp new file mode 100644 index 0000000000..1ee2044126 --- /dev/null +++ b/example/common/rfc7231.hpp @@ -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 +#include + +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 +bool +is_expect_100_continue(beast::http::request< + Body, beast::http::basic_fields> const& req) +{ + return beast::iequals( + req[beast::http::field::expect], "100-continue"); +} + +} // rfc7231 + +#endif diff --git a/example/common/root_certificates.hpp b/example/common/root_certificates.hpp new file mode 100644 index 0000000000..7d935a5664 --- /dev/null +++ b/example/common/root_certificates.hpp @@ -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 +#include + +namespace ssl = boost::asio::ssl; // from + +namespace detail { + +// The template argument is gratuituous, to +// allow the implementation to be header-only. +// +template +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 diff --git a/example/common/ssl_stream.hpp b/example/common/ssl_stream.hpp new file mode 100644 index 0000000000..4238ecdeae --- /dev/null +++ b/example/common/ssl_stream.hpp @@ -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 + +#include +#include +#include +#include +#include +#include + +/** 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 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::value, + "NextLayer requirements not met"); + + using stream_type = boost::asio::ssl::stream; + + std::unique_ptr 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 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 + void + set_verify_callback(VerifyCallback callback) + { + p_->set_verify_callback(callback); + } + + template + 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 + void + handshake( + handshake_type type, ConstBufferSequence const& buffers) + { + p_->handshake(type, buffers); + } + + template + boost::system::error_code + handshake(handshake_type type, + ConstBufferSequence const& buffers, + boost::system::error_code& ec) + { + return p_->handshake(type, buffers, ec); + } + + template + 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 + 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 + 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 + std::size_t + write_some(ConstBufferSequence const& buffers) + { + return p_->write_some(buffers); + } + + template + std::size_t + write_some(ConstBufferSequence const& buffers, + boost::system::error_code& ec) + { + return p_->write_some(buffers, ec); + } + + template + 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 + std::size_t + read_some(MutableBufferSequence const& buffers) + { + return p_->read_some(buffers); + } + + template + std::size_t + read_some(MutableBufferSequence const& buffers, + boost::system::error_code& ec) + { + return p_->read_some(buffers, ec); + } + + template + 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 + friend + void + teardown(beast::websocket::teardown_tag, + ssl_stream& stream, + boost::system::error_code& ec); + + template + friend + void + async_teardown(beast::websocket::teardown_tag, + ssl_stream& 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 +inline +void +teardown(beast::websocket::teardown_tag, + ssl_stream& 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 +inline +void +async_teardown(beast::websocket::teardown_tag, + ssl_stream& 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(handler)); +} + +#endif diff --git a/example/common/write_msg.hpp b/example/common/write_msg.hpp new file mode 100644 index 0000000000..038df599f1 --- /dev/null +++ b/example/common/write_msg.hpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +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 msg; + + // Serializer for the message + beast::http::serializer sr; + + data( + Handler& handler, + AsyncWriteStream& stream_, + beast::http::message&& 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 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(h), + s, std::forward(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 + 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 composed operation. + 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 +async_write_msg( + AsyncWriteStream& stream, + beast::http::message&& msg, + WriteHandler&& handler) +{ + static_assert( + beast::is_async_write_stream::value, + "AsyncWriteStream requirements not met"); + + static_assert(beast::http::is_body::value, + "Body requirements not met"); + + static_assert(beast::http::is_body_reader::value, + "BodyReader requirements not met"); + + beast::async_completion init{handler}; + + ::detail::write_msg_op< + AsyncWriteStream, + beast::handler_type, + isRequest, Body, Fields>{ + init.completion_handler, + stream, + std::move(msg)}(); + + return init.result.get(); +} + +#endif diff --git a/example/doc/http_examples.hpp b/example/doc/http_examples.hpp new file mode 100644 index 0000000000..7371e15daa --- /dev/null +++ b/example/doc/http_examples.hpp @@ -0,0 +1,1060 @@ +// +// 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 +#include + +/* This file contains the functions and classes found in the documentation + + They are compiled and run as part of the unit tests, so you can copy + the code and use it in your own projects as a starting point for + building a network application. +*/ + +// The documentation assumes the beast::http namespace +namespace beast { +namespace http { + +//------------------------------------------------------------------------------ +// +// Example: Expect 100-continue +// +//------------------------------------------------------------------------------ + +//[example_http_send_expect_100_continue + +/** Send a request with Expect: 100-continue + + This function will send a request with the Expect: 100-continue + field by first sending the header, then waiting for a successful + response from the server before continuing to send the body. If + a non-successful server response is received, the function + returns immediately. + + @param stream The remote HTTP server stream. + + @param buffer The buffer used for reading. + + @param req The request to send. This function modifies the object: + the Expect header field is inserted into the message if it does + not already exist, and set to "100-continue". + + @param ec Set to the error, if any occurred. +*/ +template< + class SyncStream, + class DynamicBuffer, + class Body, class Allocator> +void +send_expect_100_continue( + SyncStream& stream, + DynamicBuffer& buffer, + request>& req, + error_code& ec) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + + // Insert or replace the Expect field + req.set(field::expect, "100-continue"); + + // Create the serializer + request_serializer> sr{req}; + + // Send just the header + write_header(stream, sr, ec); + if(ec) + return; + + // Read the response from the server. + // A robust client could set a timeout here. + { + response res; + read(stream, buffer, res, ec); + if(ec) + return; + if(res.result() != status::continue_) + { + // The server indicated that it will not + // accept the request, so skip sending the body. + return; + } + } + + // Server is OK with the request, send the body + write(stream, sr, ec); +} + +//] + +//[example_http_receive_expect_100_continue + +/** Receive a request, handling Expect: 100-continue if present. + + This function will read a request from the specified stream. + If the request contains the Expect: 100-continue field, a + status response will be delivered. + + @param stream The remote HTTP client stream. + + @param buffer The buffer used for reading. + + @param ec Set to the error, if any occurred. +*/ +template< + class SyncStream, + class DynamicBuffer> +void +receive_expect_100_continue( + SyncStream& stream, + DynamicBuffer& buffer, + error_code& ec) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + + // Declare a parser for a request with a string body + request_parser parser; + + // Read the header + read_header(stream, buffer, parser, ec); + if(ec) + return; + + // Check for the Expect field value + if(parser.get()[field::expect] == "100-continue") + { + // send 100 response + response res; + res.version = 11; + res.result(status::continue_); + res.set(field::server, "test"); + write(stream, res, ec); + if(ec) + return; + } + + // Read the rest of the message. + // + // We use parser.base() to return a basic_parser&, to avoid an + // ambiguous function error (from boost::asio::read). Another + // solution is to qualify the call, e.g. `beast::http::read` + // + read(stream, buffer, parser.base(), ec); +} + +//] + +//------------------------------------------------------------------------------ +// +// Example: Send Child Process Output +// +//------------------------------------------------------------------------------ + +//[example_http_send_cgi_response + +/** Send the output of a child process as an HTTP response. + + The output of the child process comes from a @b SyncReadStream. Data + will be sent continuously as it is produced, without the requirement + that the entire process output is buffered before being sent. The + response will use the chunked transfer encoding. + + @param input A stream to read the child process output from. + + @param output A stream to write the HTTP response to. + + @param ec Set to the error, if any occurred. +*/ +template< + class SyncReadStream, + class SyncWriteStream> +void +send_cgi_response( + SyncReadStream& input, + SyncWriteStream& output, + error_code& ec) +{ + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + + // Set up the response. We use the buffer_body type, + // allowing serialization to use manually provided buffers. + response res; + + res.result(status::ok); + res.version = 11; + res.set(field::server, "Beast"); + res.set(field::transfer_encoding, "chunked"); + + // No data yet, but we set more = true to indicate + // that it might be coming later. Otherwise the + // serializer::is_done would return true right after + // sending the header. + res.body.data = nullptr; + res.body.more = true; + + // Create the serializer. + response_serializer sr{res}; + + // Send the header immediately. + write_header(output, sr, ec); + if(ec) + return; + + // Alternate between reading from the child process + // and sending all the process output until there + // is no more output. + do + { + // Read a buffer from the child process + char buffer[2048]; + auto bytes_transferred = input.read_some( + boost::asio::buffer(buffer, sizeof(buffer)), ec); + if(ec == boost::asio::error::eof) + { + ec = {}; + + // `nullptr` indicates there is no buffer + res.body.data = nullptr; + + // `false` means no more data is coming + res.body.more = false; + } + else + { + if(ec) + return; + + // Point to our buffer with the bytes that + // we received, and indicate that there may + // be some more data coming + res.body.data = buffer; + res.body.size = bytes_transferred; + res.body.more = true; + } + + // Write everything in the body buffer + write(output, sr, ec); + + // This error is returned by body_buffer during + // serialization when it is done sending the data + // provided and needs another buffer. + if(ec == error::need_buffer) + { + ec = {}; + continue; + } + if(ec) + return; + } + while(! sr.is_done()); +} + +//] + +//-------------------------------------------------------------------------- +// +// Example: HEAD Request +// +//-------------------------------------------------------------------------- + +//[example_http_do_head_response + +/** Handle a HEAD request for a resource. +*/ +template< + class SyncStream, + class DynamicBuffer +> +void do_server_head( + SyncStream& stream, + DynamicBuffer& buffer, + error_code& ec) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirments not met"); + + // We deliver this payload for all GET requests + static std::string const payload = "Hello, world!"; + + // Read the request + request req; + read(stream, buffer, req, ec); + if(ec) + return; + + // Set up the response, starting with the common fields + response res; + res.version = 11; + res.set(field::server, "test"); + + // Now handle request-specific fields + switch(req.method()) + { + case verb::head: + case verb::get: + { + // A HEAD request is handled by delivering the same + // set of headers that would be sent for a GET request, + // including the Content-Length, except for the body. + res.result(status::ok); + res.set(field::content_length, payload.size()); + + // For GET requests, we include the body + if(req.method() == verb::get) + { + // We deliver the same payload for GET requests + // regardless of the target. A real server might + // deliver a file based on the target. + res.body = payload; + } + break; + } + + default: + { + // We return responses indicating an error if + // we do not recognize the request method. + res.result(status::bad_request); + res.set(field::content_type, "text/plain"); + res.body = "Invalid request-method '" + req.method_string().to_string() + "'"; + res.prepare_payload(); + break; + } + } + + // Send the response + write(stream, res, ec); + if(ec) + return; +} + +//] + +//[example_http_do_head_request + +/** Send a HEAD request for a resource. + + This function submits a HEAD request for the specified resource + and returns the response. + + @param res The response. This is an output parameter. + + @param stream The synchronous stream to use. + + @param buffer The buffer to use. + + @param target The request target. + + @param ec Set to the error, if any occurred. + + @throws std::invalid_argument if target is empty. +*/ +template< + class SyncStream, + class DynamicBuffer +> +response +do_head_request( + SyncStream& stream, + DynamicBuffer& buffer, + string_view target, + error_code& ec) +{ + // Do some type checking to be a good citizen + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirments not met"); + + // The interfaces we are using are low level and do not + // perform any checking of arguments; so we do it here. + if(target.empty()) + throw std::invalid_argument("target may not be empty"); + + // Build the HEAD request for the target + request req; + req.version = 11; + req.method(verb::head); + req.target(target); + req.set(field::user_agent, "test"); + + // A client MUST send a Host header field in all HTTP/1.1 request messages. + // https://tools.ietf.org/html/rfc7230#section-5.4 + req.set(field::host, "localhost"); + + // Now send it + write(stream, req, ec); + if(ec) + return {}; + + // Create a parser to read the response. + // Responses to HEAD requests MUST NOT include + // a body, so we use the `empty_body` type and + // only attempt to read the header. + parser p; + read_header(stream, buffer, p, ec); + if(ec) + return {}; + + // Transfer ownership of the response to the caller. + return p.release(); +} + +//] + +//------------------------------------------------------------------------------ +// +// Example: HTTP Relay +// +//------------------------------------------------------------------------------ + +//[example_http_relay + +/** Relay an HTTP message. + + This function efficiently relays an HTTP message from a downstream + client to an upstream server, or from an upstream server to a + downstream client. After the message header is read from the input, + a user provided transformation function is invoked which may change + the contents of the header before forwarding to the output. This may + be used to adjust fields such as Server, or proxy fields. + + @param output The stream to write to. + + @param input The stream to read from. + + @param buffer The buffer to use for the input. + + @param transform The header transformation to apply. The function will + be called with this signature: + @code + template + void transform(message< + isRequest, Body, Fields>&, // The message to transform + error_code&); // Set to the error, if any + @endcode + + @param ec Set to the error if any occurred. + + @tparam isRequest `true` to relay a request. + + @tparam Fields The type of fields to use for the message. +*/ +template< + bool isRequest, + class SyncWriteStream, + class SyncReadStream, + class DynamicBuffer, + class Transform> +void +relay( + SyncWriteStream& output, + SyncReadStream& input, + DynamicBuffer& buffer, + error_code& ec, + Transform&& transform) +{ + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + + // A small buffer for relaying the body piece by piece + char buf[2048]; + + // Create a parser with a buffer body to read from the input. + parser p; + + // Create a serializer from the message contained in the parser. + serializer sr{p.get()}; + + // Read just the header from the input + read_header(input, buffer, p, ec); + if(ec) + return; + + // Apply the caller's header tranformation + transform(p.get(), ec); + if(ec) + return; + + // Send the transformed message to the output + write_header(output, sr, ec); + if(ec) + return; + + // Loop over the input and transfer it to the output + do + { + if(! p.is_done()) + { + // Set up the body for writing into our small buffer + p.get().body.data = buf; + p.get().body.size = sizeof(buf); + + // Read as much as we can + read(input, buffer, p, ec); + + // This error is returned when buffer_body uses up the buffer + if(ec == error::need_buffer) + ec = {}; + if(ec) + return; + + // Set up the body for reading. + // This is how much was parsed: + p.get().body.size = sizeof(buf) - p.get().body.size; + p.get().body.data = buf; + p.get().body.more = ! p.is_done(); + } + else + { + p.get().body.data = nullptr; + p.get().body.size = 0; + } + + // Write everything in the buffer (which might be empty) + write(output, sr, ec); + + // This error is returned when buffer_body uses up the buffer + if(ec == error::need_buffer) + ec = {}; + if(ec) + return; + } + while(! p.is_done() && ! sr.is_done()); +} + +//] + +//------------------------------------------------------------------------------ +// +// Example: Serialize to std::ostream +// +//------------------------------------------------------------------------------ + +//[example_http_write_ostream + +// The detail namespace means "not public" +namespace detail { + +// This helper is needed for C++11. +// When invoked with a buffer sequence, writes the buffers `to the std::ostream`. +template +class write_ostream_helper +{ + Serializer& sr_; + std::ostream& os_; + +public: + write_ostream_helper(Serializer& sr, std::ostream& os) + : sr_(sr) + , os_(os) + { + } + + // This function is called by the serializer + template + void + operator()(error_code& ec, ConstBufferSequence const& buffers) const + { + // These asio functions are needed to access a buffer's contents + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + + // Error codes must be cleared on success + ec = {}; + + // Keep a running total of how much we wrote + std::size_t bytes_transferred = 0; + + // Loop over the buffer sequence + for(auto it = buffers.begin(); it != buffers.end(); ++ it) + { + // This is the next buffer in the sequence + boost::asio::const_buffer const buffer = *it; + + // Write it to the std::ostream + os_.write( + buffer_cast(buffer), + buffer_size(buffer)); + + // If the std::ostream fails, convert it to an error code + if(os_.fail()) + { + ec = make_error_code(errc::io_error); + return; + } + + // Adjust our running total + bytes_transferred += buffer_size(buffer); + } + + // Inform the serializer of the amount we consumed + sr_.consume(bytes_transferred); + } +}; + +} // detail + +/** Write a message to a `std::ostream`. + + This function writes the serialized representation of the + HTTP/1 message to the sream. + + @param os The `std::ostream` to write to. + + @param msg The message to serialize. + + @param ec Set to the error, if any occurred. +*/ +template< + bool isRequest, + class Body, + class Fields> +void +write_ostream( + std::ostream& os, + message& msg, + error_code& ec) +{ + // Create the serializer instance + serializer sr{msg}; + + // This lambda is used as the "visit" function + detail::write_ostream_helper lambda{sr, os}; + do + { + // In C++14 we could use a generic lambda but since we want + // to require only C++11, the lambda is written out by hand. + // This function call retrieves the next serialized buffers. + sr.next(ec, lambda); + if(ec) + return; + } + while(! sr.is_done()); +} + +//] + +//------------------------------------------------------------------------------ +// +// Example: Parse from std::istream +// +//------------------------------------------------------------------------------ + +//[example_http_read_istream + +/** Read a message from a `std::istream`. + + This function attempts to parse a complete HTTP/1 message from the stream. + + @param is The `std::istream` to read from. + + @param buffer The buffer to use. + + @param msg The message to store the result. + + @param ec Set to the error, if any occurred. +*/ +template< + class Allocator, + bool isRequest, + class Body> +void +read_istream( + std::istream& is, + basic_flat_buffer& buffer, + message& msg, + error_code& ec) +{ + // Create the message parser + // + // Arguments passed to the parser's constructor are + // forwarded to the message constructor. Here, we use + // a move construction in case the caller has constructed + // their message in a non-default way. + // + parser p{std::move(msg)}; + + do + { + // Extract whatever characters are presently available in the istream + if(is.rdbuf()->in_avail() > 0) + { + // Get a mutable buffer sequence for writing + auto const mb = buffer.prepare( + static_cast(is.rdbuf()->in_avail())); + + // Now get everything we can from the istream + buffer.commit(static_cast(is.readsome( + boost::asio::buffer_cast(mb), + boost::asio::buffer_size(mb)))); + } + else if(buffer.size() == 0) + { + // Our buffer is empty and we need more characters, + // see if we've reached the end of file on the istream + if(! is.eof()) + { + // Get a mutable buffer sequence for writing + auto const mb = buffer.prepare(1024); + + // Try to get more from the istream. This might block. + is.read( + boost::asio::buffer_cast(mb), + boost::asio::buffer_size(mb)); + + // If an error occurs on the istream then return it to the caller. + if(is.fail() && ! is.eof()) + { + // We'll just re-use io_error since std::istream has no error_code interface. + ec = make_error_code(errc::io_error); + return; + } + + // Commit the characters we got to the buffer. + buffer.commit(static_cast(is.gcount())); + } + else + { + // Inform the parser that we've reached the end of the istream. + p.put_eof(ec); + if(ec) + return; + break; + } + } + + // Write the data to the parser + auto const bytes_used = p.put(buffer.data(), ec); + + // This error means that the parser needs additional octets. + if(ec == error::need_more) + ec = {}; + if(ec) + return; + + // Consume the buffer octets that were actually parsed. + buffer.consume(bytes_used); + } + while(! p.is_done()); + + // Transfer ownership of the message container in the parser to the caller. + msg = p.release(); +} + +//] + +//------------------------------------------------------------------------------ +// +// Example: Deferred Body Type +// +//------------------------------------------------------------------------------ + +//[example_http_defer_body + +/** Handle a form PUT request, choosing a body type depending on the Content-Type. + + This reads a request from the input stream. If the method is POST, and + the Content-Type is "application/x-www-form-urlencoded " or + "multipart/form-data", a `string_body` is used to receive and store + the message body. Otherwise, a `dynamic_body` is used to store the message + body. After the request is received, the handler will be invoked with the + request. + + @param stream The stream to read from. + + @param buffer The buffer to use for reading. + + @param handler The handler to invoke when the request is complete. + The handler must be invokable with this signature: + @code + template + void handler(request&& req); + @endcode + + @throws system_error Thrown on failure. +*/ +template< + class SyncReadStream, + class DynamicBuffer, + class Handler> +void +do_form_request( + SyncReadStream& stream, + DynamicBuffer& buffer, + Handler&& handler) +{ + // Start with an empty_body parser + request_parser req0; + + // Read just the header. Otherwise, the empty_body + // would generate an error if body octets were received. + read_header(stream, buffer, req0); + + // Choose a body depending on the method verb + switch(req0.get().method()) + { + case verb::post: + { + // If this is not a form upload then use a string_body + if( req0.get()[field::content_type] != "application/x-www-form-urlencoded" && + req0.get()[field::content_type] != "multipart/form-data") + goto do_string_body; + + // Commit to string_body as the body type. + // As long as there are no body octets in the parser + // we are constructing from, no exception is thrown. + request_parser req{std::move(req0)}; + + // Finish reading the message + read(stream, buffer, req); + + // Call the handler. It can take ownership + // if desired, since we are calling release() + handler(req.release()); + break; + } + + do_string_body: + default: + { + // Commit to dynamic_body as the body type. + // As long as there are no body octets in the parser + // we are constructing from, no exception is thrown. + request_parser req{std::move(req0)}; + + // Finish reading the message + read(stream, buffer, req); + + // Call the handler. It can take ownership + // if desired, since we are calling release() + handler(req.release()); + break; + } + } +} + +//] + + + +//------------------------------------------------------------------------------ +// +// Example: Custom Parser +// +//------------------------------------------------------------------------------ + +//[example_http_custom_parser + +template +class custom_parser + : public basic_parser> +{ + // The friend declaration is needed, + // otherwise the callbacks must be made public. + friend class basic_parser; + + /// Called after receiving the request-line (isRequest == true). + void + on_request( + verb method, // The method verb, verb::unknown if no match + string_view method_str, // The method as a string + string_view target, // The request-target + int version, // The HTTP-version + error_code& ec); // The error returned to the caller, if any + + /// Called after receiving the start-line (isRequest == false). + void + on_response( + int code, // The status-code + string_view reason, // The obsolete reason-phrase + int version, // The HTTP-version + error_code& ec); // The error returned to the caller, if any + + /// Called after receiving a header field. + void + on_field( + field f, // The known-field enumeration constant + string_view name, // The field name string. + string_view value, // The field value + error_code& ec); // The error returned to the caller, if any + + /// Called after the complete header is received. + void + on_header( + error_code& ec); // The error returned to the caller, if any + + /// Called just before processing the body, if a body exists. + void + on_body(boost::optional< + std::uint64_t> const& + content_length, // Content length if known, else `boost::none` + error_code& ec); // The error returned to the caller, if any + + /** Called for each piece of the body, if a body exists. + + If present, the chunked Transfer-Encoding will be removed + before this callback is invoked. The function returns + the number of bytes consumed from the input buffer. + Any input octets not consumed will be will be presented + on subsequent calls. + */ + std::size_t + on_data( + string_view s, // A portion of the body + error_code& ec); // The error returned to the caller, if any + + /// Called for each chunk header. + void + on_chunk( + std::uint64_t size, // The size of the upcoming chunk + string_view extension, // The chunk-extension (may be empty) + error_code& ec); // The error returned to the caller, if any + + /// Called when the complete message is parsed. + void + on_complete(error_code& ec); + +public: + custom_parser() = default; +}; + +//] + +// Definitions are not part of the docs but necessary to link + +template +void custom_parser:: +on_request(verb method, string_view method_str, + string_view path, int version, error_code& ec) +{ + boost::ignore_unused(method, method_str, path, version); + ec = {}; +} + +template +void custom_parser:: +on_response(int status, string_view reason, + int version, error_code& ec) +{ + boost::ignore_unused(status, reason, version); + ec = {}; +} + +template +void custom_parser:: +on_field(field f, string_view name, + string_view value, error_code& ec) +{ + boost::ignore_unused(f, name, value); + ec = {}; +} + +template +void custom_parser:: +on_header(error_code& ec) +{ + ec = {}; +} + +template +void custom_parser:: +on_body(boost::optional const& content_length, + error_code& ec) +{ + boost::ignore_unused(content_length); + ec = {}; +} + +template +std::size_t custom_parser:: +on_data(string_view s, error_code& ec) +{ + boost::ignore_unused(s); + ec = {}; + return s.size(); +} + +template +void custom_parser:: +on_chunk(std::uint64_t size, + string_view extension, error_code& ec) +{ + boost::ignore_unused(size, extension); + ec = {}; +} + +template +void custom_parser:: +on_complete(error_code& ec) +{ + ec = {}; +} + +//------------------------------------------------------------------------------ +// +// Example: Incremental Read +// +//------------------------------------------------------------------------------ + +//[example_incremental_read + +/* This function reads a message using a fixed size buffer to hold + portions of the body, and prints the body contents to a `std::ostream`. +*/ +template< + bool isRequest, + class SyncReadStream, + class DynamicBuffer> +void +read_and_print_body( + std::ostream& os, + SyncReadStream& stream, + DynamicBuffer& buffer, + error_code& ec) +{ + parser p; + read_header(stream, buffer, p, ec); + if(ec) + return; + while(! p.is_done()) + { + char buf[512]; + p.get().body.data = buf; + p.get().body.size = sizeof(buf); + read(stream, buffer, p, ec); + if(ec == error::need_buffer) + ec.assign(0, ec.category()); + if(ec) + return; + os.write(buf, sizeof(buf) - p.get().body.size); + } +} + +//] + +} // http +} // beast diff --git a/example/echo-op/CMakeLists.txt b/example/echo-op/CMakeLists.txt new file mode 100644 index 0000000000..d6af84394c --- /dev/null +++ b/example/echo-op/CMakeLists.txt @@ -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) diff --git a/example/echo-op/Jamfile b/example/echo-op/Jamfile new file mode 100644 index 0000000000..cfc32495a8 --- /dev/null +++ b/example/echo-op/Jamfile @@ -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 + : + coverage:no + ubasan:no + ; diff --git a/example/echo-op/echo_op.cpp b/example/echo-op/echo_op.cpp new file mode 100644 index 0000000000..150c76fc48 --- /dev/null +++ b/example/echo-op/echo_op.cpp @@ -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 +#include +#include +#include +#include +#include + +//[example_core_echo_op_1 + +template< + class AsyncStream, + class CompletionToken> +auto +async_echo(AsyncStream& stream, CompletionToken&& token) + +//] + -> beast::async_return_type; + +//[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 composed operation. 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 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> 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::max)(), + beast::handler_alloc{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 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 + echo_op(AsyncStream& stream, DeducedHandler&& handler) + : p_(std::forward(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 + friend void asio_handler_invoke( + Function&& f, echo_op* op); + + template + friend void* asio_handler_allocate( + std::size_t size, echo_op* op); + + template + friend void asio_handler_deallocate( + void* p, std::size_t size, echo_op* op); + + template + friend bool asio_handler_is_continuation( + echo_op* 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 +void echo_op:: +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 +void asio_handler_invoke( + Function&& f, echo_op* op) +{ + using boost::asio::asio_handler_invoke; + return asio_handler_invoke(f, std::addressof(op->p_.handler())); +} + +template +void* asio_handler_allocate( + std::size_t size, echo_op* op) +{ + using boost::asio::asio_handler_allocate; + return asio_handler_allocate(size, std::addressof(op->p_.handler())); +} + +template +void asio_handler_deallocate( + void* p, std::size_t size, echo_op* 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 +bool asio_handler_is_continuation(echo_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->p_->step > 1 || + asio_handler_is_continuation(std::addressof(op->p_.handler())); +} + +//] + +//[example_core_echo_op_3 + +template +class echo_op; + +// Read a line and echo it back +// +template +beast::async_return_type +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::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 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>{ + 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 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; +} diff --git a/example/http-client-ssl/CMakeLists.txt b/example/http-client-ssl/CMakeLists.txt new file mode 100644 index 0000000000..3abb8b8d5c --- /dev/null +++ b/example/http-client-ssl/CMakeLists.txt @@ -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} + ) diff --git a/examples/ssl/Jamfile.v2 b/example/http-client-ssl/Jamfile similarity index 84% rename from examples/ssl/Jamfile.v2 rename to example/http-client-ssl/Jamfile index 45ab791f90..d322b946a6 100644 --- a/examples/ssl/Jamfile.v2 +++ b/example/http-client-ssl/Jamfile @@ -43,12 +43,9 @@ project crypto ; -exe http-ssl-example - : - http_ssl_example.cpp - ; - -exe websocket-ssl-example - : - websocket_ssl_example.cpp - ; +exe http-client-ssl : + http_client_ssl.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/http-client-ssl/http_client_ssl.cpp b/example/http-client-ssl/http_client_ssl.cpp new file mode 100644 index 0000000000..046b21fcf7 --- /dev/null +++ b/example/http-client-ssl/http_client_ssl.cpp @@ -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 +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace ssl = boost::asio::ssl; // from +namespace http = beast::http; // from + +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 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 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 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; +} diff --git a/example/http-client/CMakeLists.txt b/example/http-client/CMakeLists.txt new file mode 100644 index 0000000000..59044b4744 --- /dev/null +++ b/example/http-client/CMakeLists.txt @@ -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) diff --git a/example/http-client/Jamfile b/example/http-client/Jamfile new file mode 100644 index 0000000000..a39360eaed --- /dev/null +++ b/example/http-client/Jamfile @@ -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 + : + coverage:no + ubasan:no + ; diff --git a/example/http-client/http_client.cpp b/example/http-client/http_client.cpp new file mode 100644 index 0000000000..ff4b2039f5 --- /dev/null +++ b/example/http-client/http_client.cpp @@ -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 +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace http = beast::http; // from + +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 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 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; +} + +//] diff --git a/example/http-crawl/CMakeLists.txt b/example/http-crawl/CMakeLists.txt new file mode 100644 index 0000000000..477f699c87 --- /dev/null +++ b/example/http-crawl/CMakeLists.txt @@ -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) diff --git a/examples/Jamfile.v2 b/example/http-crawl/Jamfile similarity index 59% rename from examples/Jamfile.v2 rename to example/http-crawl/Jamfile index c80330b610..180be9ec07 100644 --- a/examples/Jamfile.v2 +++ b/example/http-crawl/Jamfile @@ -8,20 +8,7 @@ exe http-crawl : http_crawl.cpp urls_large_data.cpp - ; - -exe http-server : - http_server.cpp - ; - -exe http-example : - http_example.cpp - ; - -exe websocket-echo : - websocket_echo.cpp - ; - -exe websocket-example : - websocket_example.cpp + : + coverage:no + ubasan:no ; diff --git a/example/http-crawl/http_crawl.cpp b/example/http-crawl/http_crawl.cpp new file mode 100644 index 0000000000..03ae517277 --- /dev/null +++ b/example/http-crawl/http_crawl.cpp @@ -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 +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace http = beast::http; // from + +template +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 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 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; + } +} diff --git a/examples/urls_large_data.cpp b/example/http-crawl/urls_large_data.cpp similarity index 100% rename from examples/urls_large_data.cpp rename to example/http-crawl/urls_large_data.cpp diff --git a/examples/urls_large_data.hpp b/example/http-crawl/urls_large_data.hpp similarity index 75% rename from examples/urls_large_data.hpp rename to example/http-crawl/urls_large_data.hpp index 74147a8a28..da4d8eb533 100644 --- a/examples/urls_large_data.hpp +++ b/example/http-crawl/urls_large_data.hpp @@ -5,8 +5,8 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef URLS_LARGE_DATA_H_INCLUDED -#define URLS_LARGE_DATA_H_INCLUDED +#ifndef BEAST_EXAMPLE_HTTP_CRAWL_URLS_LARGE_DATA_HPP +#define BEAST_EXAMPLE_HTTP_CRAWL_URLS_LARGE_DATA_HPP #include diff --git a/example/http-server-fast/CMakeLists.txt b/example/http-server-fast/CMakeLists.txt new file mode 100644 index 0000000000..5668ec1bd2 --- /dev/null +++ b/example/http-server-fast/CMakeLists.txt @@ -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} + ) + diff --git a/example/http-server-fast/Jamfile b/example/http-server-fast/Jamfile new file mode 100644 index 0000000000..3285b7ac17 --- /dev/null +++ b/example/http-server-fast/Jamfile @@ -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 + : + coverage:no + ubasan:no + ; diff --git a/example/http-server-fast/fields_alloc.hpp b/example/http-server-fast/fields_alloc.hpp new file mode 100644 index 0000000000..7550ccb00d --- /dev/null +++ b/example/http-server-fast/fields_alloc.hpp @@ -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 +#include +#include +#include + +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(this+1) + size_; + } + + explicit + static_pool(std::size_t size) + : size_(size) + , p_(reinterpret_cast(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(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(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 +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 + struct rebind + { + using other = fields_alloc; + }; + + explicit + fields_alloc(std::size_t size) + : pool_(detail::static_pool::construct(size)) + { + } + + fields_alloc(fields_alloc const& other) + : pool_(other.pool_.share()) + { + } + + template + fields_alloc(fields_alloc const& other) + : pool_(other.pool_.share()) + { + } + + ~fields_alloc() + { + pool_.destroy(); + } + + value_type* + allocate(size_type n) + { + return static_cast( + pool_.alloc(n * sizeof(T))); + } + + void + deallocate(value_type*, size_type) + { + pool_.dealloc(); + } + +#if defined(BOOST_LIBSTDCXX_VERSION) && BOOST_LIBSTDCXX_VERSION < 60000 + template + void + construct(U* ptr, Args&&... args) + { + ::new((void*)ptr) U(std::forward(args)...); + } + + template + void + destroy(U* ptr) + { + ptr->~U(); + } +#endif + + template + friend + bool + operator==( + fields_alloc const& lhs, + fields_alloc const& rhs) + { + return &lhs.pool_ == &rhs.pool_; + } + + template + friend + bool + operator!=( + fields_alloc const& lhs, + fields_alloc const& rhs) + { + return ! (lhs == rhs); + } +}; + +#endif diff --git a/example/http-server-fast/http_server_fast.cpp b/example/http-server-fast/http_server_fast.cpp new file mode 100644 index 0000000000..bdcea2843f --- /dev/null +++ b/example/http-server-fast/http_server_fast.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ip = boost::asio::ip; // from +using tcp = boost::asio::ip::tcp; // from +namespace http = beast::http; // from + +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; + using request_body_t = http::basic_dynamic_body>; + + // 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> parser_; + + // The timer putting a time limit on requests. + boost::asio::basic_waitable_timer request_deadline_{ + acceptor_.get_io_service(), (std::chrono::steady_clock::time_point::max)()}; + + // The string-based response message. + boost::optional>> string_response_; + + // The string-based response serializer. + boost::optional>> string_serializer_; + + // The file-based response message. + boost::optional>> file_response_; + + // The file-based response serializer. + boost::optional>> 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> 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
{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(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 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; + } +} diff --git a/example/http-server-small/CMakeLists.txt b/example/http-server-small/CMakeLists.txt new file mode 100644 index 0000000000..dfb1ade71c --- /dev/null +++ b/example/http-server-small/CMakeLists.txt @@ -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 + ) + diff --git a/example/http-server-small/Jamfile b/example/http-server-small/Jamfile new file mode 100644 index 0000000000..2acfe2cb11 --- /dev/null +++ b/example/http-server-small/Jamfile @@ -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 + : + coverage:no + ubasan:no + ; diff --git a/example/http-server-small/http_server_small.cpp b/example/http-server-small/http_server_small.cpp new file mode 100644 index 0000000000..f73c7734b0 --- /dev/null +++ b/example/http-server-small/http_server_small.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ip = boost::asio::ip; // from +using tcp = boost::asio::ip::tcp; // from +namespace http = beast::http; // from + +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 +{ +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 request_; + + // The response message. + http::response response_; + + // The timer for putting a deadline on connection processing. + boost::asio::basic_waitable_timer 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) + << "\n" + << "Request count\n" + << "\n" + << "

Request count

\n" + << "

There have been " + << my_program_state::request_count() + << " requests so far.

\n" + << "\n" + << "\n"; + } + else if(request_.target() == "/time") + { + response_.set(http::field::content_type, "text/html"); + beast::ostream(response_.body) + << "\n" + << "Current time\n" + << "\n" + << "

Current time

\n" + << "

The current time is " + << my_program_state::now() + << " seconds since the epoch.

\n" + << "\n" + << "\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(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] << "
\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(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; + } +} diff --git a/example/http-server-threaded/CMakeLists.txt b/example/http-server-threaded/CMakeLists.txt new file mode 100644 index 0000000000..a11dbfa055 --- /dev/null +++ b/example/http-server-threaded/CMakeLists.txt @@ -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} + ) + diff --git a/example/http-server-threaded/Jamfile b/example/http-server-threaded/Jamfile new file mode 100644 index 0000000000..f2f5f16e54 --- /dev/null +++ b/example/http-server-threaded/Jamfile @@ -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 + : + coverage:no + ubasan:no + ; diff --git a/example/http-server-threaded/http_server_threaded.cpp b/example/http-server-threaded/http_server_threaded.cpp new file mode 100644 index 0000000000..e75b3796b6 --- /dev/null +++ b/example/http-server-threaded/http_server_threaded.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//------------------------------------------------------------------------------ +// +// Example: HTTP server, synchronous, one thread per connection +// +//------------------------------------------------------------------------------ + +namespace ip = boost::asio::ip; // from +using tcp = boost::asio::ip::tcp; // from +namespace http = beast::http; // from + +class connection + : public std::enable_shared_from_this +{ + 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> + client_error(http::status result, beast::string_view text) + { + http::response> 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 + not_found() const + { + http::response 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 + server_error(beast::error_code const& ec) const + { + http::response 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 + get(boost::filesystem::path const& full_path, + beast::error_code& ec) const + { + http::response 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().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 + void + do_request(http::request 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 sr{res}; + http::write(sock_, sr, ec); + } + } + + void + do_run() + { + try + { + beast::error_code ec; + beast::flat_buffer buffer; + for(;;) + { + http::request_parser 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
\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(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(std::move(sock), doc_root)->run(); + } + } + catch (const std::exception& e) + { + std::cerr << "Exception: " << e.what() << std::endl; + return EXIT_FAILURE; + } +} diff --git a/example/server-framework/CMakeLists.txt b/example/server-framework/CMakeLists.txt new file mode 100644 index 0000000000..c111c3529b --- /dev/null +++ b/example/server-framework/CMakeLists.txt @@ -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() diff --git a/example/server-framework/Jamfile b/example/server-framework/Jamfile new file mode 100644 index 0000000000..80d0406ba9 --- /dev/null +++ b/example/server-framework/Jamfile @@ -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 + : + coverage:no + ubasan:no + ; diff --git a/example/server-framework/README.md b/example/server-framework/README.md new file mode 100644 index 0000000000..25bdc41151 --- /dev/null +++ b/example/server-framework/README.md @@ -0,0 +1,159 @@ +Beast + +# 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: + +ServerFramework + +## 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 + void send(beast::http::response&& 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&& 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 + void + on_upgrade( + Stream&& stream, + endpoint_type ep, + beast::http::request&& req); + +``` diff --git a/example/server-framework/file_service.hpp b/example/server-framework/file_service.hpp new file mode 100644 index 0000000000..8c7056d8d3 --- /dev/null +++ b/example/server-framework/file_service.hpp @@ -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 +#include +#include +#include +#include + +#include + +#include + +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 + void + send(response&&); + + @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&& 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 + beast::http::response + not_found( + beast::http::request const& req, + boost::filesystem::path const& rel_path) const + { + boost::ignore_unused(rel_path); + beast::http::response 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 + beast::http::response + server_error( + beast::http::request const& req, + boost::filesystem::path const& rel_path, + error_code const& ec) const + { + boost::ignore_unused(rel_path); + beast::http::response 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 + boost::optional> + get( + beast::http::request const& req, + boost::filesystem::path const& full_path, + beast::error_code& ec) const + { + beast::http::response 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().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 + boost::optional> + head( + beast::http::request const& req, + boost::filesystem::path const& full_path, + beast::error_code& ec) const + { + beast::http::response 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().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 diff --git a/example/server-framework/framework.hpp b/example/server-framework/framework.hpp new file mode 100644 index 0000000000..6c288c93ab --- /dev/null +++ b/example/server-framework/framework.hpp @@ -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 +#include +#include +#include +#include + +/** 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 base_from_member +{ +public: + template + explicit + base_from_member(Args&&... args) + : member(std::forward(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 diff --git a/example/server-framework/http_async_port.hpp b/example/server-framework/http_async_port.hpp new file mode 100644 index 0000000000..7231ca7c41 --- /dev/null +++ b/example/server-framework/http_async_port.hpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +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 msg_; + + // The handler to invoke when the send completes. + Handler handler_; + +public: + // Constructor. + // + // Ownership of the message is transferred into the object + // + template + queued_http_write_impl( + Stream& stream, + beast::http::message&& msg, + DeducedHandler&& handler) + : stream_(stream) + , msg_(std::move(msg)) + , handler_(std::forward(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 +make_queued_http_write( + Stream& stream, + beast::http::message&& msg, + Handler&& handler) +{ + return std::unique_ptr{ + new queued_http_write_impl< + Stream, + isRequest, Body, Fields, + typename std::decay::type>{ + stream, + std::move(msg), + std::forward(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 async_http_con_base : public http_base +{ +protected: + // This function lets us access members of the derived class + Derived& + impl() + { + return static_cast(*this); + } + + // The stream to use for logging + std::ostream& log_; + + // The services configured for the port + service_list 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> parser_; + + // This is the queue of outgoing messages + std::vector> 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 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 + 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 + void + operator()(beast::http::response&& 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 + void + do_write(beast::http::response&& 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 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> + + // The stream should be created before the base class so + // use the "base from member" idiom. + // + , public base_from_member + + // Constructs last, destroys first + // + , public async_http_con_base, Services...> +{ +public: + // Constructor + // + // Additional arguments are forwarded to the base class + // + template + async_http_con( + socket_type&& sock, + Args&&... args) + : base_from_member(std::move(sock)) + , async_http_con_base, Services...>( + std::forward(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, 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 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_; + +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 + void + init(error_code& ec, Args&&... args) + { + services_.template init( + ec, + std::forward(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>( + std::move(sock), + "http_async_port", + log_, + services_, + instance_.next_id(), + ep)->run(); + } +}; + +} // framework + +#endif diff --git a/example/server-framework/http_base.hpp b/example/server-framework/http_base.hpp new file mode 100644 index 0000000000..6dddb079a4 --- /dev/null +++ b/example/server-framework/http_base.hpp @@ -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 +#include +#include +#include +#include +#include +#include + +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 + beast::http::response + bad_request(beast::http::request const& req) const + { + beast::http::response 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 + beast::http::response + continue_100(beast::http::request const& req) const + { + beast::http::response 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 diff --git a/example/server-framework/http_sync_port.hpp b/example/server-framework/http_sync_port.hpp new file mode 100644 index 0000000000..2cd20477bb --- /dev/null +++ b/example/server-framework/http_sync_port.hpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 sync_http_con_base + : public http_base +{ + // This function lets us access members of the derived class + Derived& + impl() + { + return static_cast(*this); + } + + // The stream to use for logging + std::ostream& log_; + + // The services configured for the port + service_list 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 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 + void + operator()( + beast::http::response&& res) const + { + beast::http::serializer 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 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 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> + + // The stream should be created before the base class so + // use the "base from member" idiom. + // + , public base_from_member + + // Constructs last, destroys first + // + , public sync_http_con_base, Services...> +{ +public: + // Constructor + // + // Additional arguments are forwarded to the base class + // + template + sync_http_con( + socket_type&& sock, + Args&&... args) + : base_from_member(std::move(sock)) + , sync_http_con_base, Services...>( + std::forward(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, 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 http_sync_port +{ + server& instance_; + std::ostream& log_; + service_list 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 + void + init(error_code& ec, Args&&... args) + { + services_.template init( + ec, + std::forward(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>( + std::move(sock), + "http_sync_port", + log_, + services_, + instance_.next_id(), + ep)->run(); + } +}; + +} // framework + +#endif diff --git a/example/server-framework/https_ports.hpp b/example/server-framework/https_ports.hpp new file mode 100644 index 0000000000..8a4a5c2450 --- /dev/null +++ b/example/server-framework/https_ports.hpp @@ -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 + +namespace framework { + +//------------------------------------------------------------------------------ + +// This class represents a synchronous HTTP connection which +// uses an OpenSSL socket as the stream. +// +template +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> + + // The stream should be created before the base class so + // use the "base from member" idiom. + // + , public base_from_member> + + // Constructs last, destroys first + // + , public sync_http_con_base, Services...> +{ +public: + // Constructor + // + // Additional arguments are forwarded to the base class + // + template + sync_https_con( + socket_type&& sock, + boost::asio::ssl::context& ctx, + Args&&... args) + : base_from_member>(std::move(sock), ctx) + , sync_http_con_base, Services...>( + std::forward(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& + stream() + { + return this->member; + } + +private: + friend class sync_http_con_base, 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 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> + + // The stream should be created before the base class so + // use the "base from member" idiom. + // + , public base_from_member> + + // Constructs last, destroys first + // + , public async_http_con_base, Services...> +{ +public: + // Constructor + // + // Additional arguments are forwarded to the base class + // + template + async_https_con( + socket_type&& sock, + boost::asio::ssl::context& ctx, + Args&&... args) + : base_from_member>(std::move(sock), ctx) + , async_http_con_base, Services...>( + std::forward(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& + stream() + { + return this->member; + } + + // Called by the multi-port after reading some + // bytes from the stream and detecting SSL. + // + template + 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, 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 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_; + + // 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 + void + init(error_code& ec, Args&&... args) + { + services_.template init( + ec, + std::forward(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>( + 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 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_; + + // 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 + void + init(error_code& ec, Args&&... args) + { + services_.template init( + ec, + std::forward(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>( + std::move(sock), + ctx_, + "https_async_port", + log_, + services_, + instance_.next_id(), + ep)->run(); + } +}; + +} // framework + +#endif diff --git a/example/server-framework/main.cpp b/example/server-framework/main.cpp new file mode 100644 index 0000000000..fcaffa8ce7 --- /dev/null +++ b/example/server-framework/main.cpp @@ -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 + +#include + +/// 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 + void + operator()(beast::websocket::stream& 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()->default_value("."), + "Set the root directory for serving files") + ("port,p", po::value()->default_value(1000), + "Set the base port number for the server") + ("ip", po::value()->default_value("0.0.0.0"), + "Set the IP address to bind to, \"0.0.0.0\" for all") + ("threads,n", po::value()->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(); + + // Get the port number from the options + std::uint16_t const port = vm["port"].as(); + + // 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(); + + // Get the root path from the command line + boost::filesystem::path const root = vm["root"].as(); + + // 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( + ec, + endpoint_type{addr,static_cast(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, + file_service + >>( + ec, + endpoint_type{addr,static_cast(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( + ec, + endpoint_type{addr, + static_cast(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, + file_service + >>( + ec, + endpoint_type{addr, + static_cast(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( + ec, + endpoint_type{addr, + static_cast(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, + file_service + >>( + ec, + endpoint_type{addr, + static_cast(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( + ec, + endpoint_type{addr, + static_cast(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, + file_service + >>( + ec, + endpoint_type{addr, + static_cast(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, + file_service + >>( + ec, + endpoint_type{addr, + static_cast(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(); +} diff --git a/example/server-framework/multi_port.hpp b/example/server-framework/multi_port.hpp new file mode 100644 index 0000000000..3774133229 --- /dev/null +++ b/example/server-framework/multi_port.hpp @@ -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 + +#include + +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 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> + + // The stream should be created before the base class so + // use the "base from member" idiom. + // + , public base_from_member + + // Constructs last, destroys first + // + , public async_http_con_base, 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 + multi_con( + socket_type&& sock, + boost::asio::ssl::context& ctx, + Args&&... args) + : base_from_member(std::move(sock)) + , async_http_con_base, Services...>(std::forward(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, 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>( + 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&)>; + using on_new_stream_cb2 = boost::function>&)>; + + // 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 + void callback(beast::websocket::stream&); + @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 + 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 + void + on_upgrade( + socket_type&& sock, + endpoint_type ep, + beast::http::request&& req) + { + // Create the connection and call the version of + // run that takes the request since we have it already + // + std::make_shared( + 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 + void + on_upgrade( + ssl_stream&& stream, + endpoint_type ep, + beast::http::request&& req) + { + std::make_shared( + 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 multi_port : public multi_port_base +{ + // The list of services connections created from this port will support + service_list services_; + +public: + /** Constructor + + All arguments are forwarded to the multi_port_base constructor. + */ + template + multi_port(Args&&... args) + : multi_port_base(std::forward(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 + void + init(error_code& ec, Args&&... args) + { + services_.template init( + ec, + std::forward(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>( + std::move(sock), + ctx_, + "multi_port", + log_, + services_, + instance_.next_id(), + ep)->detect(); + } +}; + +} // framework + +#endif diff --git a/example/server-framework/server.hpp b/example/server-framework/server.hpp new file mode 100644 index 0000000000..6157714c0f --- /dev/null +++ b/example/server-framework/server.hpp @@ -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 +#include +#include +#include +#include +#include +#include + +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( + 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 tv_; + boost::optional 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 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 + std::shared_ptr + 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 port + : public std::enable_shared_from_this< + port> +{ + 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 + explicit + port(server& instance, Args&&... args) + : instance_(instance) + , handler_(std::forward(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 + 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( + 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 +std::shared_ptr +server:: +make_port( + error_code& ec, + endpoint_type const& ep, + Args&&... args) +{ + auto sp = std::make_shared>( + *this, std::forward(args)...); + sp->open(ep, ec); + if(ec) + return nullptr; + return sp->handler(); +} + +} // framework + +#endif diff --git a/example/server-framework/service_list.hpp b/example/server-framework/service_list.hpp new file mode 100644 index 0000000000..71d73ae793 --- /dev/null +++ b/example/server-framework/service_list.hpp @@ -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 +#include +#include + +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 service_list +{ + // This helper is for tag-dispatching tuple index + template + using C = std::integral_constant; + + // 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...> 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 + void + init(error_code& ec, Args&&... args) + { + // First, construct the service inside the optional + std::get(list_).emplace(std::forward(args)...); + + // Now allow the service to finish the initialization + std::get(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 + void + send(response&&); + + @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&& 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&&, + Send const&, + C 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&& req, + Send const& send, + C const&) const + { + // If the I-th service handles the request then return + // + if(std::get(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{}); + } +}; + +} // framework + +#endif diff --git a/example/server-framework/ssl_certificate.hpp b/example/server-framework/ssl_certificate.hpp new file mode 100644 index 0000000000..aaa2e0b853 --- /dev/null +++ b/example/server-framework/ssl_certificate.hpp @@ -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 +#include +#include +#include + +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 + 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 +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 diff --git a/example/server-framework/ws_async_port.hpp b/example/server-framework/ws_async_port.hpp new file mode 100644 index 0000000000..c799ab9f89 --- /dev/null +++ b/example/server-framework/ws_async_port.hpp @@ -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 +#include +#include +#include +#include + +namespace framework { + +// This object holds the state of the connection +// including, most importantly, the socket or stream. +// +// +template +class async_ws_con_base +{ + // This function lets us access members of the derived class + Derived& + impl() + { + return static_cast(*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 + 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 + void + run(beast::http::request 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 + + // The stream should be created before the base class so + // use the "base from member" idiom. + // + , public base_from_member> + + // Constructs last, destroys first + // + , public async_ws_con_base +{ +public: + // Constructor + // + // Additional arguments are forwarded to the base class + // + template + explicit + async_ws_con( + socket_type&& sock, + Args&&... args) + : base_from_member>(std::move(sock)) + , async_ws_con_base(std::forward(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& + stream() + { + return this->member; + } + +private: + // Base class needs to be a friend to call our private members + friend async_ws_con_base; + + 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&)>; + + 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 + void callback(beast::websocket::stream&); + @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 + 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( + 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 + void + on_upgrade( + socket_type&& sock, + endpoint_type ep, + beast::http::request&& req) + { + std::make_shared( + std::move(sock), + "ws_async_port", + log_, + instance_.next_id(), + ep, + cb_)->run(std::move(req)); + } +}; + +} // framework + +#endif diff --git a/example/server-framework/ws_sync_port.hpp b/example/server-framework/ws_sync_port.hpp new file mode 100644 index 0000000000..4ebea95fa5 --- /dev/null +++ b/example/server-framework/ws_sync_port.hpp @@ -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 +#include +#include +#include +#include +#include + +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 sync_ws_con_base +{ + // This function lets us access members of the derived class + Derived& + impl() + { + return static_cast(*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 + 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 + void + run(beast::http::request&& 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{ + 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 lambda + { + std::shared_ptr self_; + beast::http::request req_; + + public: + // Constructor + // + // This is the equivalent of the capture section of the lambda. + // + lambda( + std::shared_ptr self, + beast::http::request&& 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 + + // The stream should be created before the base class so + // use the "base from member" idiom. + // + , public base_from_member> + + // Constructs last, destroys first + // + , public sync_ws_con_base +{ +public: + // Construct the plain connection. + // + template + explicit + sync_ws_con( + socket_type&& sock, + Args&&... args) + : base_from_member>(std::move(sock)) + , sync_ws_con_base(std::forward(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& + stream() + { + return this->member; + } + +private: + // Base class needs to be a friend to call our private members + friend class sync_ws_con_base; + + // 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&)>; + + 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 + void callback(beast::websocket::stream&); + @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 + 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( + 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 + void + on_upgrade( + socket_type&& sock, + endpoint_type ep, + beast::http::request&& req) + { + // Create the connection object and run it, + // transferring ownership of the ugprade request. + // + std::make_shared( + std::move(sock), + "ws_sync_port", + log_, + instance_.next_id(), + ep, + cb_)->run(std::move(req)); + } +}; + +} // framework + +#endif diff --git a/example/server-framework/ws_upgrade_service.hpp b/example/server-framework/ws_upgrade_service.hpp new file mode 100644 index 0000000000..d4d8f5d614 --- /dev/null +++ b/example/server-framework/ws_upgrade_service.hpp @@ -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 +#include +#include + +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 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&& 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 diff --git a/example/server-framework/wss_ports.hpp b/example/server-framework/wss_ports.hpp new file mode 100644 index 0000000000..ff7f87e1ae --- /dev/null +++ b/example/server-framework/wss_ports.hpp @@ -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 +#include + +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 + + // The stream should be created before the base class so + // use the "base from member" idiom. + // + , public base_from_member>> + + // Constructs last, destroys first + // + , public sync_ws_con_base +{ +public: + // Constructor + // + // Additional arguments are forwarded to the base class + // + template + explicit + sync_wss_con( + socket_type&& sock, + boost::asio::ssl::context& ctx, + Args&&... args) + : base_from_member>>(std::move(sock), ctx) + , sync_ws_con_base(std::forward(args)...) + { + } + + // Construct from an existing, handshaked SSL stream + // + template + sync_wss_con( + ssl_stream&& stream, + Args&&... args) + : base_from_member>>(std::move(stream)) + , sync_ws_con_base(std::forward(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>& + stream() + { + return this->member; + } + +private: + friend class sync_ws_con_base; + + // 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 + + // The stream should be created before the base class so + // use the "base from member" idiom. + // + , public base_from_member>> + + // Constructs last, destroys first + // + , public async_ws_con_base +{ +public: + // Constructor + // + // Additional arguments are forwarded to the base class + // + template + async_wss_con( + socket_type&& sock, + boost::asio::ssl::context& ctx, + Args&&... args) + : base_from_member>>(std::move(sock), ctx) + , async_ws_con_base(std::forward(args)...) + { + } + + // Construct from an existing, handshaked SSL stream + // + template + async_wss_con( + ssl_stream&& stream, + Args&&... args) + : base_from_member>>(std::move(stream)) + , async_ws_con_base(std::forward(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>& + stream() + { + return this->member; + } + +private: + friend class async_ws_con_base; + + // 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&)>; + + using on_new_stream_cb2 = + boost::function>&)>; + + 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 + void callback(beast::websocket::stream&); + @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 + 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( + 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 + void + on_upgrade( + ssl_stream&& stream, + endpoint_type ep, + beast::http::request&& req) + { + // Create the connection object and run it, + // transferring ownership of the ugprade request. + // + std::make_shared( + 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&)>; + + using on_new_stream_cb2 = + boost::function>&)>; + + // 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 + void callback(beast::websocket::stream&); + @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 + 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( + 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 + void + on_upgrade( + ssl_stream&& stream, + endpoint_type ep, + beast::http::request&& req) + { + std::make_shared( + std::move(stream), + "wss_async_port", + log_, + instance_.next_id(), + ep, + cb2_)->run(std::move(req)); + } +}; + +} // framework + +#endif diff --git a/example/websocket-client-ssl/CMakeLists.txt b/example/websocket-client-ssl/CMakeLists.txt new file mode 100644 index 0000000000..676c2784c3 --- /dev/null +++ b/example/websocket-client-ssl/CMakeLists.txt @@ -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} + ) diff --git a/example/websocket-client-ssl/Jamfile b/example/websocket-client-ssl/Jamfile new file mode 100644 index 0000000000..a0a0a2a75d --- /dev/null +++ b/example/websocket-client-ssl/Jamfile @@ -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 : : ssleay32 ; + lib crypto : : libeay32 ; +} +else +{ + lib ssl ; + lib crypto ; +} + +project + : requirements + ssl + crypto + ; + +exe ssl-websocket-client : + ssl_websocket_client.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/websocket-client-ssl/websocket_client_ssl.cpp b/example/websocket-client-ssl/websocket_client_ssl.cpp new file mode 100644 index 0000000000..eb739f0302 --- /dev/null +++ b/example/websocket-client-ssl/websocket_client_ssl.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace ssl = boost::asio::ssl; // from +namespace websocket = beast::websocket; // from + +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; + 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 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; +} diff --git a/example/websocket-client/CMakeLists.txt b/example/websocket-client/CMakeLists.txt new file mode 100644 index 0000000000..0e97d94750 --- /dev/null +++ b/example/websocket-client/CMakeLists.txt @@ -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) diff --git a/example/websocket-client/Jamfile b/example/websocket-client/Jamfile new file mode 100644 index 0000000000..9dc2a5d440 --- /dev/null +++ b/example/websocket-client/Jamfile @@ -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 + : + coverage:no + ubasan:no + ; diff --git a/example/websocket-client/websocket_client.cpp b/example/websocket-client/websocket_client.cpp new file mode 100644 index 0000000000..642bfe6f1d --- /dev/null +++ b/example/websocket-client/websocket_client.cpp @@ -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 +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace websocket = beast::websocket; // from + +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 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; +} + +//] diff --git a/example/websocket-server-async/CMakeLists.txt b/example/websocket-server-async/CMakeLists.txt new file mode 100644 index 0000000000..b0f5ad1854 --- /dev/null +++ b/example/websocket-server-async/CMakeLists.txt @@ -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 + ) diff --git a/example/websocket-server-async/Jamfile b/example/websocket-server-async/Jamfile new file mode 100644 index 0000000000..59571f2fd8 --- /dev/null +++ b/example/websocket-server-async/Jamfile @@ -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 + : + coverage:no + ubasan:no + ; diff --git a/example/websocket-server-async/websocket_server_async.cpp b/example/websocket-server-async/websocket_server_async.cpp new file mode 100644 index 0000000000..55978f0ab3 --- /dev/null +++ b/example/websocket-server-async/websocket_server_async.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace http = beast::http; // from +namespace websocket = beast::websocket; // from +namespace ip = boost::asio::ip; // from +using tcp = boost::asio::ip::tcp; // from + +//------------------------------------------------------------------------------ +// +// 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; // 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 thread_; // Threads for the io_service + boost::asio::ip::tcp::acceptor acceptor_; // The listening socket + std::function 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 + { + 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 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(*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 + 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 + void + operator()(websocket::stream& 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] << "
\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(std::atoi(argv[2])); + unsigned short threads = static_cast(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; +} diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt deleted file mode 100644 index edb8e3339e..0000000000 --- a/examples/CMakeLists.txt +++ /dev/null @@ -1,76 +0,0 @@ -# Part of Beast - -GroupSources(extras/beast extras) -GroupSources(include/beast beast) - -GroupSources(examples "/") - -add_executable (http-crawl - ${BEAST_INCLUDES} - ${EXTRAS_INCLUDES} - urls_large_data.hpp - urls_large_data.cpp - http_crawl.cpp -) - -if (NOT WIN32) - target_link_libraries(http-crawl ${Boost_LIBRARIES} Threads::Threads) -else() - target_link_libraries(http-crawl ${Boost_LIBRARIES}) -endif() - -add_executable (http-server - ${BEAST_INCLUDES} - ${EXTRAS_INCLUDES} - file_body.hpp - mime_type.hpp - http_async_server.hpp - http_sync_server.hpp - http_server.cpp -) - -if (NOT WIN32) - target_link_libraries(http-server ${Boost_LIBRARIES} Threads::Threads) -else() - target_link_libraries(http-server ${Boost_LIBRARIES}) -endif() - - -add_executable (http-example - ${BEAST_INCLUDES} - ${EXTRAS_INCLUDES} - http_example.cpp -) - -if (NOT WIN32) - target_link_libraries(http-example ${Boost_LIBRARIES} Threads::Threads) -else() - target_link_libraries(http-example ${Boost_LIBRARIES}) -endif() - - -add_executable (websocket-echo - ${BEAST_INCLUDES} - websocket_async_echo_server.hpp - websocket_sync_echo_server.hpp - websocket_echo.cpp -) - -if (NOT WIN32) - target_link_libraries(websocket-echo ${Boost_LIBRARIES} Threads::Threads) -else() - target_link_libraries(websocket-echo ${Boost_LIBRARIES}) -endif() - - -add_executable (websocket-example - ${BEAST_INCLUDES} - ${EXTRAS_INCLUDES} - websocket_example.cpp -) - -if (NOT WIN32) - target_link_libraries(websocket-example ${Boost_LIBRARIES} Threads::Threads) -else() - target_link_libraries(websocket-example ${Boost_LIBRARIES}) -endif() diff --git a/examples/file_body.hpp b/examples/file_body.hpp deleted file mode 100644 index 00f2b5434d..0000000000 --- a/examples/file_body.hpp +++ /dev/null @@ -1,97 +0,0 @@ -// -// 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_FILE_BODY_H_INCLUDED -#define BEAST_EXAMPLE_FILE_BODY_H_INCLUDED - -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -struct file_body -{ - using value_type = std::string; - - class writer - { - std::uint64_t size_ = 0; - std::uint64_t offset_ = 0; - std::string const& path_; - FILE* file_ = nullptr; - char buf_[4096]; - std::size_t buf_len_; - - public: - writer(writer const&) = delete; - writer& operator=(writer const&) = delete; - - template - writer(message const& m) noexcept - : path_(m.body) - { - } - - ~writer() - { - if(file_) - fclose(file_); - } - - void - init(error_code& ec) noexcept - { - file_ = fopen(path_.c_str(), "rb"); - if(! file_) - ec = error_code{errno, - system_category()}; - else - size_ = boost::filesystem::file_size(path_); - } - - std::uint64_t - content_length() const noexcept - { - return size_; - } - - template - bool - write(error_code& ec, WriteFunction&& wf) noexcept - { - if(size_ - offset_ < sizeof(buf_)) - buf_len_ = static_cast( - size_ - offset_); - else - buf_len_ = sizeof(buf_); - auto const nread = fread( - buf_, 1, sizeof(buf_), file_); - if(ferror(file_)) - { - ec = error_code(errno, - system_category()); - return true; - } - BOOST_ASSERT(nread != 0); - offset_ += nread; - wf(boost::asio::buffer(buf_, nread)); - return offset_ >= size_; - } - }; -}; - -} // http -} // beast - -#endif diff --git a/examples/http_async_server.hpp b/examples/http_async_server.hpp deleted file mode 100644 index 33ad51a853..0000000000 --- a/examples/http_async_server.hpp +++ /dev/null @@ -1,323 +0,0 @@ -// -// 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_ASYNC_SERVER_H_INCLUDED -#define BEAST_EXAMPLE_HTTP_ASYNC_SERVER_H_INCLUDED - -#include "file_body.hpp" -#include "mime_type.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -class http_async_server -{ - using endpoint_type = boost::asio::ip::tcp::endpoint; - using address_type = boost::asio::ip::address; - using socket_type = boost::asio::ip::tcp::socket; - - using req_type = request; - using resp_type = response; - - std::mutex m_; - bool log_ = true; - boost::asio::io_service ios_; - boost::asio::ip::tcp::acceptor acceptor_; - socket_type sock_; - std::string root_; - std::vector thread_; - -public: - http_async_server(endpoint_type const& ep, - std::size_t threads, std::string const& root) - : acceptor_(ios_) - , sock_(ios_) - , root_(root) - { - acceptor_.open(ep.protocol()); - acceptor_.bind(ep); - acceptor_.listen( - boost::asio::socket_base::max_connections); - acceptor_.async_accept(sock_, - std::bind(&http_async_server::on_accept, this, - beast::asio::placeholders::error)); - thread_.reserve(threads); - for(std::size_t i = 0; i < threads; ++i) - thread_.emplace_back( - [&] { ios_.run(); }); - } - - ~http_async_server() - { - error_code ec; - ios_.dispatch( - [&]{ acceptor_.close(ec); }); - for(auto& t : thread_) - t.join(); - } - - template - void - log(Args const&... args) - { - if(log_) - { - std::lock_guard lock(m_); - log_args(args...); - } - } - -private: - template - class write_op - { - struct data - { - bool cont; - Stream& s; - message m; - - data(Handler& handler, Stream& s_, - message&& m_) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , s(s_) - , m(std::move(m_)) - { - } - }; - - handler_ptr d_; - - public: - write_op(write_op&&) = default; - write_op(write_op const&) = default; - - template - write_op(DeducedHandler&& h, Stream& s, Args&&... args) - : d_(std::forward(h), - s, std::forward(args)...) - { - (*this)(error_code{}, false); - } - - void - operator()(error_code ec, bool again = true) - { - auto& d = *d_; - d.cont = d.cont || again; - if(! again) - { - beast::http::async_write(d.s, d.m, std::move(*this)); - return; - } - d_.invoke(ec); - } - - friend - void* asio_handler_allocate( - std::size_t size, write_op* op) - { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); - } - - friend - void asio_handler_deallocate( - void* p, std::size_t size, write_op* op) - { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); - } - - friend - bool asio_handler_is_continuation(write_op* op) - { - return op->d_->cont; - } - - template - friend - void asio_handler_invoke(Function&& f, write_op* op) - { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); - } - }; - - template - static - void - async_write(Stream& stream, message< - isRequest, Body, Fields>&& msg, - DeducedHandler&& handler) - { - write_op::type, - isRequest, Body, Fields>{std::forward( - handler), stream, std::move(msg)}; - } - - class peer : public std::enable_shared_from_this - { - int id_; - streambuf sb_; - socket_type sock_; - http_async_server& server_; - boost::asio::io_service::strand strand_; - req_type req_; - - public: - peer(peer&&) = default; - peer(peer const&) = default; - peer& operator=(peer&&) = delete; - peer& operator=(peer const&) = delete; - - peer(socket_type&& sock, http_async_server& server) - : sock_(std::move(sock)) - , server_(server) - , strand_(sock_.get_io_service()) - { - static int n = 0; - id_ = ++n; - } - - void - fail(error_code ec, std::string what) - { - if(ec != boost::asio::error::operation_aborted) - server_.log("#", id_, " ", what, ": ", ec.message(), "\n"); - } - - void run() - { - do_read(); - } - - void do_read() - { - async_read(sock_, sb_, req_, strand_.wrap( - std::bind(&peer::on_read, shared_from_this(), - asio::placeholders::error))); - } - - void on_read(error_code const& ec) - { - if(ec) - return fail(ec, "read"); - auto path = req_.url; - if(path == "/") - path = "/index.html"; - path = server_.root_ + path; - if(! boost::filesystem::exists(path)) - { - response res; - res.status = 404; - res.reason = "Not Found"; - res.version = req_.version; - res.fields.insert("Server", "http_async_server"); - res.fields.insert("Content-Type", "text/html"); - res.body = "The file '" + path + "' was not found"; - prepare(res); - async_write(sock_, std::move(res), - std::bind(&peer::on_write, shared_from_this(), - asio::placeholders::error)); - return; - } - try - { - resp_type res; - res.status = 200; - res.reason = "OK"; - res.version = req_.version; - res.fields.insert("Server", "http_async_server"); - res.fields.insert("Content-Type", mime_type(path)); - res.body = path; - prepare(res); - async_write(sock_, std::move(res), - std::bind(&peer::on_write, shared_from_this(), - asio::placeholders::error)); - } - catch(std::exception const& e) - { - response res; - res.status = 500; - res.reason = "Internal Error"; - res.version = req_.version; - res.fields.insert("Server", "http_async_server"); - res.fields.insert("Content-Type", "text/html"); - res.body = - std::string{"An internal error occurred"} + e.what(); - prepare(res); - async_write(sock_, std::move(res), - std::bind(&peer::on_write, shared_from_this(), - asio::placeholders::error)); - } - } - - void on_write(error_code ec) - { - if(ec) - fail(ec, "write"); - do_read(); - } - }; - - void - log_args() - { - } - - template - void - log_args(Arg const& arg, Args const&... args) - { - std::cerr << arg; - log_args(args...); - } - - void - fail(error_code ec, std::string what) - { - log(what, ": ", ec.message(), "\n"); - } - - void - on_accept(error_code ec) - { - if(! acceptor_.is_open()) - return; - if(ec) - return fail(ec, "accept"); - socket_type sock(std::move(sock_)); - acceptor_.async_accept(sock_, - std::bind(&http_async_server::on_accept, this, - asio::placeholders::error)); - std::make_shared(std::move(sock), *this)->run(); - } -}; - -} // http -} // beast - -#endif diff --git a/examples/http_crawl.cpp b/examples/http_crawl.cpp deleted file mode 100644 index 1a794169b0..0000000000 --- a/examples/http_crawl.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// -// 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 -#include -#include -#include -#include - -using namespace beast::http; -using namespace boost::asio; - -template -void -err(beast::error_code const& ec, String const& what) -{ - std::cerr << what << ": " << ec.message() << std::endl; -} - -int main(int, char const*[]) -{ - io_service ios; - for(auto const& host : urls_large_data()) - { - try - { - ip::tcp::resolver r(ios); - auto it = r.resolve( - ip::tcp::resolver::query{host, "http"}); - ip::tcp::socket sock(ios); - connect(sock, it); - auto ep = sock.remote_endpoint(); - request req; - req.method = "GET"; - req.url = "/"; - req.version = 11; - req.fields.insert("Host", host + std::string(":") + - boost::lexical_cast(ep.port())); - req.fields.insert("User-Agent", "beast/http"); - prepare(req); - write(sock, req); - response res; - streambuf sb; - beast::http::read(sock, sb, res); - std::cout << res; - } - catch(beast::system_error const& ec) - { - std::cerr << host << ": " << ec.what(); - } - catch(...) - { - std::cerr << host << ": unknown exception" << std::endl; - } - } -} diff --git a/examples/http_example.cpp b/examples/http_example.cpp deleted file mode 100644 index 038ac2e24d..0000000000 --- a/examples/http_example.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// -// 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 -#include -#include -#include -#include - -int main() -{ - // Normal boost::asio setup - std::string const host = "boost.org"; - boost::asio::io_service ios; - boost::asio::ip::tcp::resolver r{ios}; - boost::asio::ip::tcp::socket sock{ios}; - boost::asio::connect(sock, - r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"})); - - // Send HTTP request using beast - beast::http::request req; - req.method = "GET"; - req.url = "/"; - req.version = 11; - req.fields.replace("Host", host + ":" + - boost::lexical_cast(sock.remote_endpoint().port())); - req.fields.replace("User-Agent", "Beast"); - beast::http::prepare(req); - beast::http::write(sock, req); - - // Receive and print HTTP response using beast - beast::streambuf sb; - beast::http::response resp; - beast::http::read(sock, sb, resp); - std::cout << resp; -} diff --git a/examples/http_server.cpp b/examples/http_server.cpp deleted file mode 100644 index 7694d559b3..0000000000 --- a/examples/http_server.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// -// 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 "http_async_server.hpp" -#include "http_sync_server.hpp" - -#include -#include - -#include - -int main(int ac, char const* av[]) -{ - using namespace beast::http; - namespace po = boost::program_options; - po::options_description desc("Options"); - - desc.add_options() - ("root,r", po::value()->default_value("."), - "Set the root directory for serving files") - ("port,p", po::value()->default_value(8080), - "Set the port number for the server") - ("ip", po::value()->default_value("0.0.0.0"), - "Set the IP address to bind to, \"0.0.0.0\" for all") - ("threads,n", po::value()->default_value(4), - "Set the number of threads to use") - ("sync,s", "Launch a synchronous server") - ; - po::variables_map vm; - po::store(po::parse_command_line(ac, av, desc), vm); - - std::string root = vm["root"].as(); - - std::uint16_t port = vm["port"].as(); - - std::string ip = vm["ip"].as(); - - std::size_t threads = vm["threads"].as(); - - bool sync = vm.count("sync") > 0; - - using endpoint_type = boost::asio::ip::tcp::endpoint; - using address_type = boost::asio::ip::address; - - endpoint_type ep{address_type::from_string(ip), port}; - - if(sync) - { - http_sync_server server(ep, root); - beast::test::sig_wait(); - } - else - { - http_async_server server(ep, threads, root); - beast::test::sig_wait(); - } -} diff --git a/examples/http_sync_server.hpp b/examples/http_sync_server.hpp deleted file mode 100644 index 378775688d..0000000000 --- a/examples/http_sync_server.hpp +++ /dev/null @@ -1,217 +0,0 @@ -// -// 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_SYNC_SERVER_H_INCLUDED -#define BEAST_EXAMPLE_HTTP_SYNC_SERVER_H_INCLUDED - -#include "file_body.hpp" -#include "mime_type.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace beast { -namespace http { - -class http_sync_server -{ - using endpoint_type = boost::asio::ip::tcp::endpoint; - using address_type = boost::asio::ip::address; - using socket_type = boost::asio::ip::tcp::socket; - - using req_type = request; - using resp_type = response; - - bool log_ = true; - std::mutex m_; - boost::asio::io_service ios_; - socket_type sock_; - boost::asio::ip::tcp::acceptor acceptor_; - std::string root_; - std::thread thread_; - -public: - http_sync_server(endpoint_type const& ep, - std::string const& root) - : sock_(ios_) - , acceptor_(ios_) - , root_(root) - { - acceptor_.open(ep.protocol()); - acceptor_.bind(ep); - acceptor_.listen( - boost::asio::socket_base::max_connections); - acceptor_.async_accept(sock_, - std::bind(&http_sync_server::on_accept, this, - beast::asio::placeholders::error)); - thread_ = std::thread{[&]{ ios_.run(); }}; - } - - ~http_sync_server() - { - error_code ec; - ios_.dispatch( - [&]{ acceptor_.close(ec); }); - thread_.join(); - } - - template - void - log(Args const&... args) - { - if(log_) - { - std::lock_guard lock(m_); - log_args(args...); - } - } - -private: - void - log_args() - { - } - - template - void - log_args(Arg const& arg, Args const&... args) - { - std::cerr << arg; - log_args(args...); - } - - void - fail(error_code ec, std::string what) - { - log(what, ": ", ec.message(), "\n"); - } - - void - fail(int id, error_code const& ec) - { - if(ec != boost::asio::error::operation_aborted && - ec != boost::asio::error::eof) - log("#", id, " ", ec.message(), "\n"); - } - - struct lambda - { - int id; - http_sync_server& self; - socket_type sock; - boost::asio::io_service::work work; - - lambda(int id_, http_sync_server& self_, - socket_type&& sock_) - : id(id_) - , self(self_) - , sock(std::move(sock_)) - , work(sock.get_io_service()) - { - } - - void operator()() - { - self.do_peer(id, std::move(sock)); - } - }; - - void - on_accept(error_code ec) - { - if(! acceptor_.is_open()) - return; - if(ec) - return fail(ec, "accept"); - static int id_ = 0; - std::thread{lambda{++id_, *this, std::move(sock_)}}.detach(); - acceptor_.async_accept(sock_, - std::bind(&http_sync_server::on_accept, this, - asio::placeholders::error)); - } - - void - do_peer(int id, socket_type&& sock0) - { - socket_type sock(std::move(sock0)); - streambuf sb; - error_code ec; - for(;;) - { - req_type req; - http::read(sock, sb, req, ec); - if(ec) - break; - auto path = req.url; - if(path == "/") - path = "/index.html"; - path = root_ + path; - if(! boost::filesystem::exists(path)) - { - response res; - res.status = 404; - res.reason = "Not Found"; - res.version = req.version; - res.fields.insert("Server", "http_sync_server"); - res.fields.insert("Content-Type", "text/html"); - res.body = "The file '" + path + "' was not found"; - prepare(res); - write(sock, res, ec); - if(ec) - break; - return; - } - try - { - resp_type res; - res.status = 200; - res.reason = "OK"; - res.version = req.version; - res.fields.insert("Server", "http_sync_server"); - res.fields.insert("Content-Type", mime_type(path)); - res.body = path; - prepare(res); - write(sock, res, ec); - if(ec) - break; - } - catch(std::exception const& e) - { - response res; - res.status = 500; - res.reason = "Internal Error"; - res.version = req.version; - res.fields.insert("Server", "http_sync_server"); - res.fields.insert("Content-Type", "text/html"); - res.body = - std::string{"An internal error occurred: "} + e.what(); - prepare(res); - write(sock, res, ec); - if(ec) - break; - } - } - fail(id, ec); - } -}; - -} // http -} // beast - -#endif diff --git a/examples/mime_type.hpp b/examples/mime_type.hpp deleted file mode 100644 index babffd5f25..0000000000 --- a/examples/mime_type.hpp +++ /dev/null @@ -1,51 +0,0 @@ -// -// 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_MIME_TYPE_H_INCLUDED -#define BEAST_EXAMPLE_HTTP_MIME_TYPE_H_INCLUDED - -#include -#include - -namespace beast { -namespace http { - -// Return the Mime-Type for a given file extension -template -std::string -mime_type(std::string const& path) -{ - auto const ext = - boost::filesystem::path{path}.extension().string(); - if(ext == ".txt") return "text/plain"; - if(ext == ".htm") return "text/html"; - if(ext == ".html") return "text/html"; - if(ext == ".php") return "text/html"; - if(ext == ".css") return "text/css"; - if(ext == ".js") return "application/javascript"; - if(ext == ".json") return "application/json"; - if(ext == ".xml") return "application/xml"; - if(ext == ".swf") return "application/x-shockwave-flash"; - if(ext == ".flv") return "video/x-flv"; - if(ext == ".png") return "image/png"; - if(ext == ".jpe") return "image/jpeg"; - if(ext == ".jpeg") return "image/jpeg"; - if(ext == ".jpg") return "image/jpeg"; - if(ext == ".gif") return "image/gif"; - if(ext == ".bmp") return "image/bmp"; - if(ext == ".ico") return "image/vnd.microsoft.icon"; - if(ext == ".tiff") return "image/tiff"; - if(ext == ".tif") return "image/tiff"; - if(ext == ".svg") return "image/svg+xml"; - if(ext == ".svgz") return "image/svg+xml"; - return "application/text"; -} - -} // http -} // beast - -#endif diff --git a/examples/ssl/CMakeLists.txt b/examples/ssl/CMakeLists.txt deleted file mode 100644 index df35e5f325..0000000000 --- a/examples/ssl/CMakeLists.txt +++ /dev/null @@ -1,32 +0,0 @@ -# Part of Beast - -GroupSources(extras/beast extras) -GroupSources(include/beast beast) - -GroupSources(examples/ssl "/") - -include_directories(${OPENSSL_INCLUDE_DIR}) - -add_executable (http-ssl-example - ${BEAST_INCLUDES} - ${EXTRAS_INCLUDES} - http_ssl_example.cpp -) - -target_link_libraries(http-ssl-example ${OPENSSL_LIBRARIES}) - -if (NOT WIN32) - target_link_libraries(http-ssl-example ${Boost_LIBRARIES} Threads::Threads) -endif() - -add_executable (websocket-ssl-example - ${BEAST_INCLUDES} - ${EXTRAS_INCLUDES} - websocket_ssl_example.cpp -) - -target_link_libraries(websocket-ssl-example ${OPENSSL_LIBRARIES}) - -if (NOT WIN32) - target_link_libraries(websocket-ssl-example ${Boost_LIBRARIES} Threads::Threads) -endif() diff --git a/examples/ssl/http_ssl_example.cpp b/examples/ssl/http_ssl_example.cpp deleted file mode 100644 index 30c8194043..0000000000 --- a/examples/ssl/http_ssl_example.cpp +++ /dev/null @@ -1,58 +0,0 @@ -// -// 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 -#include -#include -#include -#include -#include - -int main() -{ - using boost::asio::connect; - using socket = boost::asio::ip::tcp::socket; - using resolver = boost::asio::ip::tcp::resolver; - using io_service = boost::asio::io_service; - namespace ssl = boost::asio::ssl; - - // Normal boost::asio setup - std::string const host = "github.com"; - io_service ios; - resolver r{ios}; - socket sock{ios}; - connect(sock, r.resolve(resolver::query{host, "https"})); - - // Perform SSL handshaking - ssl::context ctx{ssl::context::sslv23}; - ssl::stream stream{sock, ctx}; - stream.set_verify_mode(ssl::verify_none); - stream.handshake(ssl::stream_base::client); - - // Send HTTP request over SSL using Beast - beast::http::request req; - req.method = "GET"; - req.url = "/"; - req.version = 11; - req.fields.insert("Host", host + ":" + - boost::lexical_cast(sock.remote_endpoint().port())); - req.fields.insert("User-Agent", "Beast"); - beast::http::prepare(req); - beast::http::write(stream, req); - - // Receive and print HTTP response using Beast - beast::streambuf sb; - beast::http::response resp; - beast::http::read(stream, sb, resp); - std::cout << resp; - - // Shut down SSL on the stream - boost::system::error_code ec; - stream.shutdown(ec); - if(ec && ec != boost::asio::error::eof) - std::cout << "error: " << ec.message(); -} diff --git a/examples/ssl/websocket_ssl_example.cpp b/examples/ssl/websocket_ssl_example.cpp deleted file mode 100644 index bba99c7a2f..0000000000 --- a/examples/ssl/websocket_ssl_example.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// -// 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 -#include -#include -#include -#include -#include -#include - -int main() -{ - using boost::asio::connect; - using socket = boost::asio::ip::tcp::socket; - using resolver = boost::asio::ip::tcp::resolver; - using io_service = boost::asio::io_service; - namespace ssl = boost::asio::ssl; - - // Normal boost::asio setup - std::string const host = "echo.websocket.org"; - io_service ios; - resolver r{ios}; - socket sock{ios}; - connect(sock, r.resolve(resolver::query{host, "https"})); - - // Perform SSL handshaking - using stream_type = ssl::stream; - ssl::context ctx{ssl::context::sslv23}; - stream_type stream{sock, ctx}; - stream.set_verify_mode(ssl::verify_none); - stream.handshake(ssl::stream_base::client); - - // Secure WebSocket connect and send message using Beast - beast::websocket::stream ws{stream}; - ws.handshake(host, "/"); - ws.write(boost::asio::buffer("Hello, world!")); - - // Receive Secure WebSocket message, print and close using Beast - beast::streambuf sb; - beast::websocket::opcode op; - ws.read(op, sb); - ws.close(beast::websocket::close_code::normal); - std::cout << to_string(sb.data()) << "\n"; -} diff --git a/examples/websocket_async_echo_server.hpp b/examples/websocket_async_echo_server.hpp deleted file mode 100644 index bd0c8481b6..0000000000 --- a/examples/websocket_async_echo_server.hpp +++ /dev/null @@ -1,375 +0,0 @@ -// -// 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 WEBSOCKET_ASYNC_ECHO_SERVER_HPP -#define WEBSOCKET_ASYNC_ECHO_SERVER_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace websocket { - -/** Asynchronous WebSocket echo client/server -*/ -class async_echo_server -{ -public: - using error_code = beast::error_code; - using address_type = boost::asio::ip::address; - using socket_type = boost::asio::ip::tcp::socket; - using endpoint_type = boost::asio::ip::tcp::endpoint; - -private: - struct identity - { - template - void - operator()(beast::http::message< - true, Body, Fields>& req) const - { - req.fields.replace("User-Agent", "async_echo_client"); - } - - template - void - operator()(beast::http::message< - false, Body, Fields>& resp) const - { - resp.fields.replace("Server", "async_echo_server"); - } - }; - - /** A container of type-erased option setters. - */ - template - class options_set - { - // workaround for std::function bug in msvc - struct callable - { - virtual ~callable() = default; - virtual void operator()( - beast::websocket::stream&) = 0; - }; - - template - class callable_impl : public callable - { - T t_; - - public: - template - callable_impl(U&& u) - : t_(std::forward(u)) - { - } - - void - operator()(beast::websocket::stream& ws) - { - t_(ws); - } - }; - - template - class lambda - { - Opt opt_; - - public: - lambda(lambda&&) = default; - lambda(lambda const&) = default; - - lambda(Opt const& opt) - : opt_(opt) - { - } - - void - operator()(beast::websocket::stream& ws) const - { - ws.set_option(opt_); - } - }; - - std::unordered_map> list_; - - public: - template - void - set_option(Opt const& opt) - { - std::unique_ptr p; - p.reset(new callable_impl>{opt}); - list_[std::type_index{ - typeid(Opt)}] = std::move(p); - } - - void - set_options(beast::websocket::stream& ws) - { - for(auto const& op : list_) - (*op.second)(ws); - } - }; - - std::ostream* log_; - boost::asio::io_service ios_; - socket_type sock_; - endpoint_type ep_; - boost::asio::ip::tcp::acceptor acceptor_; - std::vector thread_; - boost::optional work_; - options_set opts_; - -public: - async_echo_server(async_echo_server const&) = delete; - async_echo_server& operator=(async_echo_server const&) = delete; - - /** 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. - */ - async_echo_server(std::ostream* log, - std::size_t threads) - : log_(log) - , sock_(ios_) - , acceptor_(ios_) - , work_(ios_) - { - opts_.set_option( - beast::websocket::decorate(identity{})); - thread_.reserve(threads); - for(std::size_t i = 0; i < threads; ++i) - thread_.emplace_back( - [&]{ ios_.run(); }); - } - - /** Destructor. - */ - ~async_echo_server() - { - work_ = boost::none; - error_code ec; - ios_.dispatch( - [&]{ acceptor_.close(ec); }); - for(auto& t : thread_) - t.join(); - } - - /** Return the listening endpoint. - */ - endpoint_type - local_endpoint() const - { - return acceptor_.local_endpoint(); - } - - /** Set a websocket option. - - The option will be applied to all new connections. - - @param opt The option to apply. - */ - template - void - set_option(Opt const& opt) - { - opts_.set_option(opt); - } - - /** Open a listening port. - - @param ep The address and port to bind to. - - @param ec Set to the error, if any occurred. - */ - void - open(endpoint_type 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); - acceptor_.async_accept(sock_, ep_, - std::bind(&async_echo_server::on_accept, this, - beast::asio::placeholders::error)); - } - -private: - class peer - { - struct data - { - async_echo_server& server; - endpoint_type ep; - int state = 0; - beast::websocket::stream ws; - boost::asio::io_service::strand strand; - beast::websocket::opcode op; - beast::streambuf db; - std::size_t id; - - data(async_echo_server& server_, - endpoint_type const& ep_, - socket_type&& sock_) - : server(server_) - , ep(ep_) - , ws(std::move(sock_)) - , strand(ws.get_io_service()) - , id([] - { - static std::atomic n{0}; - return ++n; - }()) - { - } - }; - - // VFALCO This could be unique_ptr in [Net.TS] - std::shared_ptr d_; - - public: - peer(peer&&) = default; - peer(peer const&) = default; - peer& operator=(peer&&) = delete; - peer& operator=(peer const&) = delete; - - template - explicit - peer(async_echo_server& server, - endpoint_type const& ep, socket_type&& sock, - Args&&... args) - : d_(std::make_shared(server, ep, - std::forward(sock), - std::forward(args)...)) - { - auto& d = *d_; - d.server.opts_.set_options(d.ws); - run(); - } - - void run() - { - auto& d = *d_; - d.ws.async_accept(std::move(*this)); - } - - void operator()(error_code ec, std::size_t) - { - (*this)(ec); - } - - void operator()(error_code ec) - { - using boost::asio::buffer; - using boost::asio::buffer_copy; - auto& d = *d_; - switch(d.state) - { - // did accept - case 0: - if(ec) - return fail("async_accept", ec); - - // start - case 1: - if(ec) - return fail("async_handshake", ec); - d.db.consume(d.db.size()); - // read message - d.state = 2; - d.ws.async_read(d.op, d.db, - d.strand.wrap(std::move(*this))); - return; - - // got message - case 2: - if(ec == beast::websocket::error::closed) - return; - if(ec) - return fail("async_read", ec); - // write message - d.state = 1; - d.ws.set_option( - beast::websocket::message_type(d.op)); - d.ws.async_write(d.db.data(), - d.strand.wrap(std::move(*this))); - return; - } - } - - private: - void - fail(std::string what, error_code ec) - { - auto& d = *d_; - if(d.server.log_) - if(ec != beast::websocket::error::closed) - d.server.fail("[#" + std::to_string(d.id) + - " " + boost::lexical_cast(d.ep) + - "] " + what, ec); - } - }; - - void - fail(std::string what, error_code ec) - { - if(log_) - { - static std::mutex m; - std::lock_guard lock{m}; - (*log_) << what << ": " << - ec.message() << std::endl; - } - } - - void - on_accept(error_code ec) - { - if(! acceptor_.is_open()) - return; - if(ec == boost::asio::error::operation_aborted) - return; - if(ec) - fail("accept", ec); - peer{*this, ep_, std::move(sock_)}; - acceptor_.async_accept(sock_, ep_, - std::bind(&async_echo_server::on_accept, this, - beast::asio::placeholders::error)); - } -}; - -} // websocket - -#endif diff --git a/examples/websocket_echo.cpp b/examples/websocket_echo.cpp deleted file mode 100644 index e8171526fd..0000000000 --- a/examples/websocket_echo.cpp +++ /dev/null @@ -1,56 +0,0 @@ -// -// 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 "websocket_async_echo_server.hpp" -#include "websocket_sync_echo_server.hpp" -#include -#include -#include - -/// Block until SIGINT or SIGTERM is received. -void -sig_wait() -{ - boost::asio::io_service ios; - boost::asio::signal_set signals( - ios, SIGINT, SIGTERM); - signals.async_wait( - [&](boost::system::error_code const&, int) - { - }); - ios.run(); -} - -int main() -{ - using namespace beast::websocket; - using endpoint_type = boost::asio::ip::tcp::endpoint; - using address_type = boost::asio::ip::address; - - beast::error_code ec; - - permessage_deflate pmd; - pmd.client_enable = true; - pmd.server_enable = true; - pmd.compLevel = 3; - - websocket::async_echo_server s1{&std::cout, 1}; - s1.set_option(read_message_max{64 * 1024 * 1024}); - s1.set_option(auto_fragment{false}); - s1.set_option(pmd); - s1.open(endpoint_type{ - address_type::from_string("127.0.0.1"), 6000 }, ec); - - websocket::sync_echo_server s2{&std::cout}; - s2.set_option(read_message_max{64 * 1024 * 1024}); - s2.set_option(auto_fragment{false}); - s2.set_option(pmd); - s2.open(endpoint_type{ - address_type::from_string("127.0.0.1"), 6001 }, ec); - - sig_wait(); -} diff --git a/examples/websocket_example.cpp b/examples/websocket_example.cpp deleted file mode 100644 index bda489be68..0000000000 --- a/examples/websocket_example.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// -// 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 -#include -#include -#include -#include - -int main() -{ - // Normal boost::asio setup - std::string const host = "echo.websocket.org"; - boost::asio::io_service ios; - boost::asio::ip::tcp::resolver r{ios}; - boost::asio::ip::tcp::socket sock{ios}; - boost::asio::connect(sock, - r.resolve(boost::asio::ip::tcp::resolver::query{host, "80"})); - - // WebSocket connect and send message using beast - beast::websocket::stream ws{sock}; - ws.handshake(host, "/"); - ws.write(boost::asio::buffer(std::string("Hello, world!"))); - - // Receive WebSocket message, print and close using beast - beast::streambuf sb; - beast::websocket::opcode op; - ws.read(op, sb); - ws.close(beast::websocket::close_code::normal); - std::cout << beast::to_string(sb.data()) << "\n"; -} diff --git a/examples/websocket_sync_echo_server.hpp b/examples/websocket_sync_echo_server.hpp deleted file mode 100644 index 1f41b52ce1..0000000000 --- a/examples/websocket_sync_echo_server.hpp +++ /dev/null @@ -1,326 +0,0 @@ -// -// 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 WEBSOCKET_SYNC_ECHO_SERVER_HPP -#define WEBSOCKET_SYNC_ECHO_SERVER_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace websocket { - -/** Synchronous WebSocket echo client/server -*/ -class sync_echo_server -{ -public: - using error_code = beast::error_code; - using endpoint_type = boost::asio::ip::tcp::endpoint; - using address_type = boost::asio::ip::address; - using socket_type = boost::asio::ip::tcp::socket; - -private: - struct identity - { - template - void - operator()(beast::http::message< - true, Body, Fields>& req) const - { - req.fields.replace("User-Agent", "sync_echo_client"); - } - - template - void - operator()(beast::http::message< - false, Body, Fields>& resp) const - { - resp.fields.replace("Server", "sync_echo_server"); - } - }; - - /** A container of type-erased option setters. - */ - template - class options_set - { - // workaround for std::function bug in msvc - struct callable - { - virtual ~callable() = default; - virtual void operator()( - beast::websocket::stream&) = 0; - }; - - template - class callable_impl : public callable - { - T t_; - - public: - template - callable_impl(U&& u) - : t_(std::forward(u)) - { - } - - void - operator()(beast::websocket::stream& ws) - { - t_(ws); - } - }; - - template - class lambda - { - Opt opt_; - - public: - lambda(lambda&&) = default; - lambda(lambda const&) = default; - - lambda(Opt const& opt) - : opt_(opt) - { - } - - void - operator()(beast::websocket::stream& ws) const - { - ws.set_option(opt_); - } - }; - - std::unordered_map> list_; - - public: - template - void - set_option(Opt const& opt) - { - std::unique_ptr p; - p.reset(new callable_impl>{opt}); - list_[std::type_index{ - typeid(Opt)}] = std::move(p); - } - - void - set_options(beast::websocket::stream& ws) - { - for(auto const& op : list_) - (*op.second)(ws); - } - }; - - std::ostream* log_; - boost::asio::io_service ios_; - socket_type sock_; - endpoint_type ep_; - boost::asio::ip::tcp::acceptor acceptor_; - std::thread thread_; - options_set opts_; - -public: - /** Constructor. - - @param log A pointer to a stream to log to, or `nullptr` - to disable logging. - */ - sync_echo_server(std::ostream* log) - : log_(log) - , sock_(ios_) - , acceptor_(ios_) - { - opts_.set_option( - beast::websocket::decorate(identity{})); - } - - /** Destructor. - */ - ~sync_echo_server() - { - if(thread_.joinable()) - { - error_code ec; - ios_.dispatch( - [&]{ acceptor_.close(ec); }); - thread_.join(); - } - } - - /** Return the listening endpoint. - */ - endpoint_type - local_endpoint() const - { - return acceptor_.local_endpoint(); - } - - /** Set a websocket option. - - The option will be applied to all new connections. - - @param opt The option to apply. - */ - template - void - set_option(Opt const& opt) - { - opts_.set_option(opt); - } - - /** Open a listening port. - - @param ep The address and port to bind to. - - @param ec Set to the error, if any occurred. - */ - void - open(endpoint_type 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); - acceptor_.async_accept(sock_, ep_, - std::bind(&sync_echo_server::on_accept, this, - beast::asio::placeholders::error)); - thread_ = std::thread{[&]{ ios_.run(); }}; - } - -private: - void - fail(std::string what, error_code ec) - { - if(log_) - { - static std::mutex m; - std::lock_guard lock{m}; - (*log_) << what << ": " << - ec.message() << std::endl; - } - } - - void - fail(std::string what, error_code ec, - int id, endpoint_type const& ep) - { - if(log_) - if(ec != beast::websocket::error::closed) - fail("[#" + std::to_string(id) + " " + - boost::lexical_cast(ep) + - "] " + what, ec); - } - - void - on_accept(error_code ec) - { - if(ec == boost::asio::error::operation_aborted) - return; - if(ec) - return fail("accept", ec); - struct lambda - { - std::size_t id; - endpoint_type ep; - sync_echo_server& self; - boost::asio::io_service::work work; - // Must be destroyed before work otherwise the - // io_service could be destroyed before the socket. - socket_type sock; - - lambda(sync_echo_server& self_, - endpoint_type const& ep_, - socket_type&& sock_) - : id([] - { - static std::atomic n{0}; - return ++n; - }()) - , ep(ep_) - , self(self_) - , work(sock_.get_io_service()) - , sock(std::move(sock_)) - { - } - - void operator()() - { - self.do_peer(id, ep, std::move(sock)); - } - }; - std::thread{lambda{*this, ep_, std::move(sock_)}}.detach(); - acceptor_.async_accept(sock_, ep_, - std::bind(&sync_echo_server::on_accept, this, - beast::asio::placeholders::error)); - } - - void - do_peer(std::size_t id, - endpoint_type const& ep, socket_type&& sock) - { - using boost::asio::buffer; - using boost::asio::buffer_copy; - beast::websocket::stream< - socket_type> ws{std::move(sock)}; - opts_.set_options(ws); - error_code ec; - ws.accept(ec); - if(ec) - { - fail("accept", ec, id, ep); - return; - } - for(;;) - { - beast::websocket::opcode op; - beast::streambuf sb; - ws.read(op, sb, ec); - if(ec) - { - auto const s = ec.message(); - break; - } - ws.set_option(beast::websocket::message_type{op}); - ws.write(sb.data(), ec); - if(ec) - break; - } - if(ec && ec != beast::websocket::error::closed) - { - fail("read", ec, id, ep); - } - } -}; - -} // websocket - -#endif diff --git a/extras/beast/doc_debug.hpp b/extras/beast/doc_debug.hpp index 5b5587f0dd..7023c8a2cf 100644 --- a/extras/beast/doc_debug.hpp +++ b/extras/beast/doc_debug.hpp @@ -10,7 +10,7 @@ namespace beast { -#if GENERATING_DOCS +#if BEAST_DOXYGEN /// doc type (documentation debug helper) using doc_type = int; diff --git a/extras/beast/test/fail_counter.hpp b/extras/beast/test/fail_counter.hpp index 7bfe486bf3..8c577cbea8 100644 --- a/extras/beast/test/fail_counter.hpp +++ b/extras/beast/test/fail_counter.hpp @@ -9,6 +9,7 @@ #define BEAST_TEST_FAIL_COUNTER_HPP #include +#include namespace beast { namespace test { @@ -82,6 +83,26 @@ make_error_code(error ev) detail::get_error_category()}; } +/** An error code with an error set on default construction + + Default constructed versions of this object will have + an error code set right away. This helps tests find code + which forgets to clear the error code on success. +*/ +struct fail_error_code : error_code +{ + fail_error_code() + : error_code(make_error_code(error::fail_error)) + { + } + + template + fail_error_code(Arg0&& arg0, ArgN&&... argn) + : error_code(arg0, std::forward(argn)...) + { + } +}; + /** A countdown to simulated failure. On the Nth operation, the class will fail with the specified @@ -114,7 +135,7 @@ public: if(n_ > 0) --n_; if(! n_) - throw system_error{ec_}; + BOOST_THROW_EXCEPTION(system_error{ec_}); } /// Set an error code on the Nth failure @@ -128,6 +149,7 @@ public: ec = ec_; return true; } + ec.assign(0, ec.category()); return false; } }; diff --git a/extras/beast/test/fail_stream.hpp b/extras/beast/test/fail_stream.hpp index 8ec63dc524..c4b38538bf 100644 --- a/extras/beast/test/fail_stream.hpp +++ b/extras/beast/test/fail_stream.hpp @@ -8,10 +8,10 @@ #ifndef BEAST_TEST_FAIL_STREAM_HPP #define BEAST_TEST_FAIL_STREAM_HPP -#include +#include #include #include -#include +#include #include #include #include @@ -36,8 +36,7 @@ public: typename std::remove_reference::type; using lowest_layer_type = - typename beast::detail::get_lowest_layer< - next_layer_type>::type; + typename get_lowest_layer::type; fail_stream(fail_stream&&) = delete; fail_stream(fail_stream const&) = delete; @@ -103,20 +102,19 @@ public: } template - typename async_completion< - ReadHandler, void(error_code)>::result_type + async_return_type< + ReadHandler, void(error_code, std::size_t)> async_read_some(MutableBufferSequence const& buffers, ReadHandler&& handler) { error_code ec; if(pfc_->fail(ec)) { - async_completion< - ReadHandler, void(error_code, std::size_t) - > completion{handler}; + async_completion init{handler}; next_layer_.get_io_service().post( - bind_handler(completion.handler, ec, 0)); - return completion.result.get(); + bind_handler(init.completion_handler, ec, 0)); + return init.result.get(); } return next_layer_.async_read_some(buffers, std::forward(handler)); @@ -140,20 +138,19 @@ public: } template - typename async_completion< - WriteHandler, void(error_code)>::result_type + async_return_type< + WriteHandler, void(error_code, std::size_t)> async_write_some(ConstBufferSequence const& buffers, WriteHandler&& handler) { error_code ec; if(pfc_->fail(ec)) { - async_completion< - WriteHandler, void(error_code, std::size_t) - > completion{handler}; + async_completion init{handler}; next_layer_.get_io_service().post( - bind_handler(completion.handler, ec, 0)); - return completion.result.get(); + bind_handler(init.completion_handler, ec, 0)); + return init.result.get(); } return next_layer_.async_write_some(buffers, std::forward(handler)); diff --git a/extras/beast/test/pipe_stream.hpp b/extras/beast/test/pipe_stream.hpp new file mode 100644 index 0000000000..b40690242a --- /dev/null +++ b/extras/beast/test/pipe_stream.hpp @@ -0,0 +1,529 @@ +// +// 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_TEST_PIPE_STREAM_HPP +#define BEAST_TEST_PIPE_STREAM_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace test { + +/** A bidirectional in-memory communication channel + + An instance of this class provides a client and server + endpoint that are automatically connected to each other + similarly to a connected socket. + + Test pipes are used to facilitate writing unit tests + where the behavior of the transport is tightly controlled + to help illuminate all code paths (for code coverage) +*/ +class pipe +{ +public: + using buffer_type = flat_buffer; + +private: + struct read_op + { + virtual ~read_op() = default; + virtual void operator()() = 0; + }; + + struct state + { + std::mutex m; + buffer_type b; + std::condition_variable cv; + std::unique_ptr op; + bool eof = false; + }; + + state s_[2]; + +public: + /** Represents an endpoint. + + Each pipe has a client stream and a server stream. + */ + class stream + { + friend class pipe; + + template + class read_op_impl; + + state& in_; + state& out_; + boost::asio::io_service& ios_; + fail_counter* fc_ = nullptr; + std::size_t read_max_ = + (std::numeric_limits::max)(); + std::size_t write_max_ = + (std::numeric_limits::max)(); + + stream(state& in, state& out, + boost::asio::io_service& ios) + : in_(in) + , out_(out) + , ios_(ios) + , buffer(in_.b) + { + } + + public: + using buffer_type = pipe::buffer_type; + + /// Direct access to the underlying buffer + buffer_type& buffer; + + /// Counts the number of read calls + std::size_t nread = 0; + + /// Counts the number of write calls + std::size_t nwrite = 0; + + ~stream() = default; + stream(stream&&) = default; + + /// Set the fail counter on the object + void + fail(fail_counter& fc) + { + fc_ = &fc; + } + + /// Return the `io_service` associated with the object + boost::asio::io_service& + get_io_service() + { + return ios_; + } + + /// Set the maximum number of bytes returned by read_some + void + read_size(std::size_t n) + { + read_max_ = n; + } + + /// Set the maximum number of bytes returned by write_some + void + write_size(std::size_t n) + { + write_max_ = n; + } + + /// Returns a string representing the pending input data + string_view + str() const + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + return { + buffer_cast(*in_.b.data().begin()), + buffer_size(*in_.b.data().begin())}; + } + + /// Clear the buffer holding the input data + void + clear() + { + in_.b.consume((std::numeric_limits< + std::size_t>::max)()); + } + + /** Close the stream. + + The other end of the pipe will see + `boost::asio::error::eof` on read. + */ + template + void + close(); + + template + std::size_t + read_some(MutableBufferSequence const& buffers); + + template + std::size_t + read_some(MutableBufferSequence const& buffers, + error_code& ec); + + template + async_return_type< + ReadHandler, void(error_code, std::size_t)> + async_read_some(MutableBufferSequence const& buffers, + ReadHandler&& handler); + + template + std::size_t + write_some(ConstBufferSequence const& buffers); + + template + std::size_t + write_some( + ConstBufferSequence const& buffers, error_code&); + + template + async_return_type< + WriteHandler, void(error_code, std::size_t)> + async_write_some(ConstBufferSequence const& buffers, + WriteHandler&& handler); + + friend + void + teardown(websocket::teardown_tag, + stream&, boost::system::error_code& ec) + { + ec.assign(0, ec.category()); + } + + template + friend + void + async_teardown(websocket::teardown_tag, + stream& s, TeardownHandler&& handler) + { + s.get_io_service().post( + bind_handler(std::move(handler), + error_code{})); + } + }; + + /** Constructor. + + The client and server endpoints will use the same `io_service`. + */ + explicit + pipe(boost::asio::io_service& ios) + : client(s_[0], s_[1], ios) + , server(s_[1], s_[0], ios) + { + } + + /** Constructor. + + The client and server endpoints will different `io_service` objects. + */ + explicit + pipe(boost::asio::io_service& ios1, + boost::asio::io_service& ios2) + : client(s_[0], s_[1], ios1) + , server(s_[1], s_[0], ios2) + { + } + + /// Represents the client endpoint + stream client; + + /// Represents the server endpoint + stream server; +}; + +//------------------------------------------------------------------------------ + +template +class pipe::stream::read_op_impl : + public pipe::read_op +{ + stream& s_; + Buffers b_; + Handler h_; + +public: + read_op_impl(stream& s, + Buffers const& b, Handler&& h) + : s_(s) + , b_(b) + , h_(std::move(h)) + { + } + + read_op_impl(stream& s, + Buffers const& b, Handler const& h) + : s_(s) + , b_(b) + , h_(h) + { + } + + void + operator()() override; +}; + +//------------------------------------------------------------------------------ + +template +void +pipe::stream:: +read_op_impl::operator()() +{ + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + s_.ios_.post( + [&]() + { + BOOST_ASSERT(s_.in_.op); + std::unique_lock lock{s_.in_.m}; + if(s_.in_.b.size() > 0) + { + auto const bytes_transferred = buffer_copy( + b_, s_.in_.b.data(), s_.read_max_); + s_.in_.b.consume(bytes_transferred); + auto& s = s_; + Handler h{std::move(h_)}; + lock.unlock(); + s.in_.op.reset(nullptr); + ++s.nread; + s.ios_.post(bind_handler(std::move(h), + error_code{}, bytes_transferred)); + } + else + { + BOOST_ASSERT(s_.in_.eof); + auto& s = s_; + Handler h{std::move(h_)}; + lock.unlock(); + s.in_.op.reset(nullptr); + ++s.nread; + s.ios_.post(bind_handler(std::move(h), + boost::asio::error::eof, 0)); + } + }); +} + +//------------------------------------------------------------------------------ + +template +void +pipe::stream:: +close() +{ + std::lock_guard lock{out_.m}; + out_.eof = true; + if(out_.op) + out_.op.get()->operator()(); + else + out_.cv.notify_all(); +} + + +template +std::size_t +pipe::stream:: +read_some(MutableBufferSequence const& buffers) +{ + static_assert(is_mutable_buffer_sequence< + MutableBufferSequence>::value, + "MutableBufferSequence requirements not met"); + error_code ec; + auto const n = read_some(buffers, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); + return n; +} + +template +std::size_t +pipe::stream:: +read_some(MutableBufferSequence const& buffers, + error_code& ec) +{ + static_assert(is_mutable_buffer_sequence< + MutableBufferSequence>::value, + "MutableBufferSequence requirements not met"); + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + BOOST_ASSERT(! in_.op); + BOOST_ASSERT(buffer_size(buffers) > 0); + if(fc_ && fc_->fail(ec)) + return 0; + std::unique_lock lock{in_.m}; + in_.cv.wait(lock, + [&]() + { + return in_.b.size() > 0 || in_.eof; + }); + std::size_t bytes_transferred; + if(in_.b.size() > 0) + { + ec.assign(0, ec.category()); + bytes_transferred = buffer_copy( + buffers, in_.b.data(), read_max_); + in_.b.consume(bytes_transferred); + } + else + { + BOOST_ASSERT(in_.eof); + bytes_transferred = 0; + ec = boost::asio::error::eof; + } + ++nread; + return bytes_transferred; +} + +template +async_return_type< + ReadHandler, void(error_code, std::size_t)> +pipe::stream:: +async_read_some(MutableBufferSequence const& buffers, + ReadHandler&& handler) +{ + static_assert(is_mutable_buffer_sequence< + MutableBufferSequence>::value, + "MutableBufferSequence requirements not met"); + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + BOOST_ASSERT(! in_.op); + BOOST_ASSERT(buffer_size(buffers) > 0); + async_completion init{handler}; + if(fc_) + { + error_code ec; + if(fc_->fail(ec)) + return ios_.post(bind_handler( + init.completion_handler, ec, 0)); + } + { + std::unique_lock lock{in_.m}; + if(in_.eof) + { + lock.unlock(); + ++nread; + ios_.post(bind_handler(init.completion_handler, + boost::asio::error::eof, 0)); + } + else if(buffer_size(buffers) == 0 || + buffer_size(in_.b.data()) > 0) + { + auto const bytes_transferred = buffer_copy( + buffers, in_.b.data(), read_max_); + in_.b.consume(bytes_transferred); + lock.unlock(); + ++nread; + ios_.post(bind_handler(init.completion_handler, + error_code{}, bytes_transferred)); + } + else + { + in_.op.reset(new read_op_impl, + MutableBufferSequence>{*this, buffers, + init.completion_handler}); + } + } + return init.result.get(); +} + +template +std::size_t +pipe::stream:: +write_some(ConstBufferSequence const& buffers) +{ + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + BOOST_ASSERT(! out_.eof); + error_code ec; + auto const bytes_transferred = + write_some(buffers, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); + return bytes_transferred; +} + +template +std::size_t +pipe::stream:: +write_some( + ConstBufferSequence const& buffers, error_code& ec) +{ + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + BOOST_ASSERT(! out_.eof); + if(fc_ && fc_->fail(ec)) + return 0; + auto const n = (std::min)( + buffer_size(buffers), write_max_); + std::unique_lock lock{out_.m}; + auto const bytes_transferred = + buffer_copy(out_.b.prepare(n), buffers); + out_.b.commit(bytes_transferred); + lock.unlock(); + if(out_.op) + out_.op.get()->operator()(); + else + out_.cv.notify_all(); + ++nwrite; + ec.assign(0, ec.category()); + return bytes_transferred; +} + +template +async_return_type< + WriteHandler, void(error_code, std::size_t)> +pipe::stream:: +async_write_some(ConstBufferSequence const& buffers, + WriteHandler&& handler) +{ + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + BOOST_ASSERT(! out_.eof); + async_completion init{handler}; + if(fc_) + { + error_code ec; + if(fc_->fail(ec)) + return ios_.post(bind_handler( + init.completion_handler, ec, 0)); + } + auto const n = + (std::min)(buffer_size(buffers), write_max_); + std::unique_lock lock{out_.m}; + auto const bytes_transferred = + buffer_copy(out_.b.prepare(n), buffers); + out_.b.commit(bytes_transferred); + lock.unlock(); + if(out_.op) + out_.op.get()->operator()(); + else + out_.cv.notify_all(); + ++nwrite; + ios_.post(bind_handler(init.completion_handler, + error_code{}, bytes_transferred)); + return init.result.get(); +} + +} // test +} // beast + +#endif diff --git a/extras/beast/test/string_iostream.hpp b/extras/beast/test/string_iostream.hpp new file mode 100644 index 0000000000..eeeb7e21ab --- /dev/null +++ b/extras/beast/test/string_iostream.hpp @@ -0,0 +1,173 @@ +// +// 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_TEST_STRING_IOSTREAM_HPP +#define BEAST_TEST_STRING_IOSTREAM_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace test { + +/** A SyncStream and AsyncStream that reads from a string and writes to another string. + + This class behaves like a socket, except that written data is + appended to a string exposed as a public data member, and when + data is read it comes from a string provided at construction. +*/ +class string_iostream +{ + std::string s_; + boost::asio::const_buffer cb_; + boost::asio::io_service& ios_; + std::size_t read_max_; + +public: + std::string str; + + string_iostream(boost::asio::io_service& ios, + std::string s, std::size_t read_max = + (std::numeric_limits::max)()) + : s_(std::move(s)) + , cb_(boost::asio::buffer(s_)) + , ios_(ios) + , read_max_(read_max) + { + } + + boost::asio::io_service& + get_io_service() + { + return ios_; + } + + template + std::size_t + read_some(MutableBufferSequence const& buffers) + { + error_code ec; + auto const n = read_some(buffers, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); + return n; + } + + template + std::size_t + read_some(MutableBufferSequence const& buffers, + error_code& ec) + { + auto const n = boost::asio::buffer_copy( + buffers, buffer_prefix(read_max_, cb_)); + if(n > 0) + { + ec.assign(0, ec.category()); + cb_ = cb_ + n; + } + else + { + ec = boost::asio::error::eof; + } + return n; + } + + template + async_return_type< + ReadHandler, void(error_code, std::size_t)> + async_read_some(MutableBufferSequence const& buffers, + ReadHandler&& handler) + { + auto const n = boost::asio::buffer_copy( + buffers, boost::asio::buffer(s_)); + error_code ec; + if(n > 0) + s_.erase(0, n); + else + ec = boost::asio::error::eof; + async_completion init{handler}; + ios_.post(bind_handler( + init.completion_handler, ec, n)); + return init.result.get(); + } + + template + std::size_t + write_some(ConstBufferSequence const& buffers) + { + error_code ec; + auto const n = write_some(buffers, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); + return n; + } + + template + std::size_t + write_some( + ConstBufferSequence const& buffers, error_code& ec) + { + ec.assign(0, ec.category()); + using boost::asio::buffer_size; + using boost::asio::buffer_cast; + auto const n = buffer_size(buffers); + str.reserve(str.size() + n); + for(boost::asio::const_buffer buffer : buffers) + str.append(buffer_cast(buffer), + buffer_size(buffer)); + return n; + } + + template + async_return_type< + WriteHandler, void(error_code, std::size_t)> + async_write_some(ConstBufferSequence const& buffers, + WriteHandler&& handler) + { + error_code ec; + auto const bytes_transferred = write_some(buffers, ec); + async_completion init{handler}; + get_io_service().post( + bind_handler(init.completion_handler, ec, bytes_transferred)); + return init.result.get(); + } + + friend + void + teardown(websocket::teardown_tag, + string_iostream&, + boost::system::error_code& ec) + { + ec.assign(0, ec.category()); + } + + template + friend + void + async_teardown(websocket::teardown_tag, + string_iostream& stream, + TeardownHandler&& handler) + { + stream.get_io_service().post( + bind_handler(std::move(handler), + error_code{})); + } +}; + +} // test +} // beast + +#endif diff --git a/extras/beast/test/string_istream.hpp b/extras/beast/test/string_istream.hpp index e7c32064e1..c9692b8726 100644 --- a/extras/beast/test/string_istream.hpp +++ b/extras/beast/test/string_istream.hpp @@ -8,12 +8,13 @@ #ifndef BEAST_TEST_STRING_ISTREAM_HPP #define BEAST_TEST_STRING_ISTREAM_HPP -#include +#include #include #include -#include +#include #include #include +#include #include namespace beast { @@ -56,7 +57,7 @@ public: error_code ec; auto const n = read_some(buffers, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); return n; } @@ -66,17 +67,22 @@ public: error_code& ec) { auto const n = boost::asio::buffer_copy( - buffers, prepare_buffer(read_max_, cb_)); + buffers, cb_, read_max_); if(n > 0) + { + ec.assign(0, ec.category()); cb_ = cb_ + n; + } else + { ec = boost::asio::error::eof; + } return n; } template - typename async_completion::result_type + async_return_type< + ReadHandler, void(error_code, std::size_t)> async_read_some(MutableBufferSequence const& buffers, ReadHandler&& handler) { @@ -88,10 +94,10 @@ public: else ec = boost::asio::error::eof; async_completion completion{handler}; + void(error_code, std::size_t)> init{handler}; ios_.post(bind_handler( - completion.handler, ec, n)); - return completion.result.get(); + init.completion_handler, ec, n)); + return init.result.get(); } template @@ -101,29 +107,51 @@ public: error_code ec; auto const n = write_some(buffers, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); return n; } template std::size_t write_some(ConstBufferSequence const& buffers, - error_code&) + error_code& ec) { + ec.assign(0, ec.category()); return boost::asio::buffer_size(buffers); } template - typename async_completion::result_type + async_return_type< + WriteHandler, void(error_code, std::size_t)> async_write_some(ConstBuffeSequence const& buffers, WriteHandler&& handler) { async_completion completion{handler}; - ios_.post(bind_handler(completion.handler, + void(error_code, std::size_t)> init{handler}; + ios_.post(bind_handler(init.completion_handler, error_code{}, boost::asio::buffer_size(buffers))); - return completion.result.get(); + return init.result.get(); + } + + friend + void + teardown(websocket::teardown_tag, + string_istream&, + boost::system::error_code& ec) + { + ec.assign(0, ec.category()); + } + + template + friend + void + async_teardown(websocket::teardown_tag, + string_istream& stream, + TeardownHandler&& handler) + { + stream.get_io_service().post( + bind_handler(std::move(handler), + error_code{})); } }; diff --git a/extras/beast/test/string_ostream.hpp b/extras/beast/test/string_ostream.hpp index a939da7aab..dd57d8a999 100644 --- a/extras/beast/test/string_ostream.hpp +++ b/extras/beast/test/string_ostream.hpp @@ -8,11 +8,14 @@ #ifndef BEAST_TEST_STRING_OSTREAM_HPP #define BEAST_TEST_STRING_OSTREAM_HPP -#include +#include #include +#include #include +#include #include #include +#include #include namespace beast { @@ -21,13 +24,17 @@ namespace test { class string_ostream { boost::asio::io_service& ios_; + std::size_t write_max_; public: std::string str; explicit - string_ostream(boost::asio::io_service& ios) + string_ostream(boost::asio::io_service& ios, + std::size_t write_max = + (std::numeric_limits::max)()) : ios_(ios) + , write_max_(write_max) { } @@ -44,29 +51,30 @@ public: error_code ec; auto const n = read_some(buffers, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); return n; } template std::size_t - read_some(MutableBufferSequence const& buffers, + read_some(MutableBufferSequence const&, error_code& ec) { + ec = boost::asio::error::eof; return 0; } template - typename async_completion::result_type - async_read_some(MutableBufferSequence const& buffers, + async_return_type< + ReadHandler, void(error_code, std::size_t)> + async_read_some(MutableBufferSequence const&, ReadHandler&& handler) { async_completion completion{handler}; - ios_.post(bind_handler(completion.handler, - error_code{}, 0)); - return completion.result.get(); + void(error_code, std::size_t)> init{handler}; + ios_.post(bind_handler(init.completion_handler, + boost::asio::error::eof, 0)); + return init.result.get(); } template @@ -76,39 +84,62 @@ public: error_code ec; auto const n = write_some(buffers, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); return n; } template std::size_t write_some( - ConstBufferSequence const& buffers, error_code&) + ConstBufferSequence const& buffers, error_code& ec) { - auto const n = buffer_size(buffers); + ec.assign(0, ec.category()); using boost::asio::buffer_size; using boost::asio::buffer_cast; + auto const n = + (std::min)(buffer_size(buffers), write_max_); str.reserve(str.size() + n); - for(auto const& buffer : buffers) + for(boost::asio::const_buffer buffer : + buffer_prefix(n, buffers)) str.append(buffer_cast(buffer), buffer_size(buffer)); return n; } template - typename async_completion< - WriteHandler, void(error_code)>::result_type + async_return_type< + WriteHandler, void(error_code, std::size_t)> async_write_some(ConstBufferSequence const& buffers, WriteHandler&& handler) { error_code ec; auto const bytes_transferred = write_some(buffers, ec); - async_completion< - WriteHandler, void(error_code, std::size_t) - > completion{handler}; + async_completion init{handler}; get_io_service().post( - bind_handler(completion.handler, ec, bytes_transferred)); - return completion.result.get(); + bind_handler(init.completion_handler, ec, bytes_transferred)); + return init.result.get(); + } + + friend + void + teardown(websocket::teardown_tag, + string_ostream&, + boost::system::error_code& ec) + { + ec.assign(0, ec.category()); + } + + template + friend + void + async_teardown(websocket::teardown_tag, + string_ostream& stream, + TeardownHandler&& handler) + { + stream.get_io_service().post( + bind_handler(std::move(handler), + error_code{})); } }; diff --git a/extras/beast/test/test_allocator.hpp b/extras/beast/test/test_allocator.hpp new file mode 100644 index 0000000000..bc468de2ae --- /dev/null +++ b/extras/beast/test/test_allocator.hpp @@ -0,0 +1,166 @@ +// +// 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_TEST_TEST_ALLOCATOR_HPP +#define BEAST_TEST_TEST_ALLOCATOR_HPP + +#include +#include +#include + +namespace beast { +namespace test { + +struct test_allocator_info +{ + std::size_t id; + std::size_t ncopy = 0; + std::size_t nmove = 0; + std::size_t nmassign = 0; + std::size_t ncpassign = 0; + std::size_t nselect = 0; + + test_allocator_info() + : id([] + { + static std::atomic sid(0); + return ++sid; + }()) + { + } +}; + +template +class test_allocator; + +template +struct test_allocator_base +{ +}; + +template +struct test_allocator_base +{ + static + test_allocator + select_on_container_copy_construction(test_allocator< + T, Equal, Assign, Move, Swap, true> const& a) + { + return test_allocator{}; + } +}; + +template +class test_allocator : public test_allocator_base< + T, Equal, Assign, Move, Swap, Select> +{ + std::shared_ptr info_; + + template + friend class test_allocator; + +public: + using value_type = T; + + using propagate_on_container_copy_assignment = + std::integral_constant; + + using propagate_on_container_move_assignment = + std::integral_constant; + + using propagate_on_container_swap = + std::integral_constant; + + template + struct rebind + { + using other = test_allocator; + }; + + test_allocator() + : info_(std::make_shared()) + { + } + + test_allocator(test_allocator const& u) noexcept + : info_(u.info_) + { + ++info_->ncopy; + } + + template + test_allocator(test_allocator const& u) noexcept + : info_(u.info_) + { + ++info_->ncopy; + } + + test_allocator(test_allocator&& t) + : info_(t.info_) + { + ++info_->nmove; + } + + test_allocator& + operator=(test_allocator const& u) noexcept + { + info_ = u.info_; + ++info_->ncpassign; + return *this; + } + + test_allocator& + operator=(test_allocator&& u) noexcept + { + info_ = u.info_; + ++info_->nmassign; + return *this; + } + + value_type* + allocate(std::size_t n) + { + return static_cast( + ::operator new (n*sizeof(value_type))); + } + + void + deallocate(value_type* p, std::size_t) noexcept + { + ::operator delete(p); + } + + bool + operator==(test_allocator const& other) const + { + return id() == other.id() || Equal; + } + + bool + operator!=(test_allocator const& other) const + { + return ! this->operator==(other); + } + + std::size_t + id() const + { + return info_->id; + } + + test_allocator_info const* + operator->() const + { + return info_.get(); + } +}; + +} // test +} // beast + +#endif diff --git a/extras/beast/test/yield_to.hpp b/extras/beast/test/yield_to.hpp index c6b3a8679a..df5b8a341d 100644 --- a/extras/beast/test/yield_to.hpp +++ b/extras/beast/test/yield_to.hpp @@ -15,6 +15,7 @@ #include #include #include +#include namespace beast { namespace test { @@ -32,30 +33,31 @@ protected: private: boost::optional work_; - std::thread thread_; + std::vector threads_; std::mutex m_; std::condition_variable cv_; - bool running_ = false; + std::size_t running_ = 0; public: /// The type of yield context passed to functions. using yield_context = boost::asio::yield_context; - enable_yield_to() + explicit + enable_yield_to(std::size_t concurrency = 1) : work_(ios_) - , thread_([&] - { - ios_.run(); - } - ) { + threads_.reserve(concurrency); + while(concurrency--) + threads_.emplace_back( + [&]{ ios_.run(); }); } ~enable_yield_to() { work_ = boost::none; - thread_.join(); + for(auto& t : threads_) + t.join(); } /// Return the `io_service` associated with the object @@ -65,61 +67,65 @@ public: return ios_; } - /** Run a function in a coroutine. + /** Run one or more functions, each in a coroutine. - This call will block until the coroutine terminates. - - Function will be called with this signature: + This call will block until all coroutines terminate. + Each functions should have this signature: @code - void f(args..., yield_context); + void f(yield_context); @endcode - @param f The Callable object to invoke. - - @param args Optional arguments forwarded to the callable object. + @param fn... One or more functions to invoke. */ -#if GENERATING_DOCS - template +#if BEAST_DOXYGEN + template void - yield_to(F&& f, Args&&... args); + yield_to(FN&&... fn) #else - template + template void - yield_to(F&& f); - - template - void - yield_to(Function&& f, Arg&& arg, Args&&... args) - { - yield_to(std::bind(f, - std::forward(arg), - std::forward(args)..., - std::placeholders::_1)); - } + yield_to(F0&& f0, FN&&... fn); #endif + +private: + void + spawn() + { + } + + template + void + spawn(F0&& f, FN&&... fn); }; -template +template void -enable_yield_to::yield_to(Function&& f) +enable_yield_to:: +yield_to(F0&& f0, FN&&... fn) +{ + running_ = 1 + sizeof...(FN); + spawn(f0, fn...); + std::unique_lock lock{m_}; + cv_.wait(lock, [&]{ return running_ == 0; }); +} + +template +inline +void +enable_yield_to:: +spawn(F0&& f, FN&&... fn) { - { - std::lock_guard lock(m_); - running_ = true; - } boost::asio::spawn(ios_, - [&](boost::asio::yield_context do_yield) + [&](yield_context yield) { - f(do_yield); - std::lock_guard lock(m_); - running_ = false; - cv_.notify_all(); + f(yield); + std::lock_guard lock{m_}; + if(--running_ == 0) + cv_.notify_all(); } , boost::coroutines::attributes(2 * 1024 * 1024)); - - std::unique_lock lock(m_); - cv_.wait(lock, [&]{ return ! running_; }); + spawn(fn...); } } // test diff --git a/extras/beast/unit_test/dstream.hpp b/extras/beast/unit_test/dstream.hpp index 44eb599beb..fe02a26edd 100644 --- a/extras/beast/unit_test/dstream.hpp +++ b/extras/beast/unit_test/dstream.hpp @@ -8,28 +8,22 @@ #ifndef BEAST_UNIT_TEST_DSTREAM_HPP #define BEAST_UNIT_TEST_DSTREAM_HPP +#include #include #include #include #include #include -#ifdef _MSC_VER -# ifndef NOMINMAX -# define NOMINMAX 1 -# endif -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# include -# undef WIN32_LEAN_AND_MEAN -# undef NOMINMAX +#ifdef BOOST_WINDOWS +#include +//#include #endif namespace beast { namespace unit_test { -#ifdef _MSC_VER +#ifdef BOOST_WINDOWS namespace detail { @@ -48,14 +42,14 @@ class dstream_buf void write(char const* s) { if(dbg_) - OutputDebugStringA(s); + /*boost::detail::winapi*/::OutputDebugStringA(s); os_ << s; } void write(wchar_t const* s) { if(dbg_) - OutputDebugStringW(s); + /*boost::detail::winapi*/::OutputDebugStringW(s); os_ << s; } @@ -63,7 +57,7 @@ public: explicit dstream_buf(ostream& os) : os_(os) - , dbg_(IsDebuggerPresent() != FALSE) + , dbg_(/*boost::detail::winapi*/::IsDebuggerPresent() != 0) { } diff --git a/extras/beast/unit_test/main.cpp b/extras/beast/unit_test/main.cpp index 93435ab6c2..e6c1464ac5 100644 --- a/extras/beast/unit_test/main.cpp +++ b/extras/beast/unit_test/main.cpp @@ -11,12 +11,13 @@ #include #include #include +#include #include #include #include #include -#ifdef _MSC_VER +#ifdef BOOST_MSVC # ifndef WIN32_LEAN_AND_MEAN // VC_EXTRALEAN # define WIN32_LEAN_AND_MEAN # include @@ -78,7 +79,7 @@ int main(int ac, char const* av[]) using namespace std; using namespace beast::unit_test; -#ifdef _MSC_VER +#if BOOST_MSVC { int flags = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); flags |= _CRTDBG_LEAK_CHECK_DF; @@ -99,7 +100,7 @@ int main(int ac, char const* av[]) po::store(po::parse_command_line(ac, av, desc), vm); po::notify(vm); - dstream log{std::cerr}; + dstream log(std::cerr); std::unitbuf(log); if(vm.count("help")) diff --git a/extras/beast/unit_test/suite.hpp b/extras/beast/unit_test/suite.hpp index 1120daf41c..ce0ec909a4 100644 --- a/extras/beast/unit_test/suite.hpp +++ b/extras/beast/unit_test/suite.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -551,7 +552,7 @@ fail(std::string const& reason) if(abort_) { aborted_ = true; - throw abort_exception(); + BOOST_THROW_EXCEPTION(abort_exception()); } } @@ -569,7 +570,7 @@ suite:: propagate_abort() { if(abort_ && aborted_) - throw abort_exception(); + BOOST_THROW_EXCEPTION(abort_exception()); } template diff --git a/include/beast.hpp b/include/beast.hpp new file mode 100644 index 0000000000..fe5add2cf4 --- /dev/null +++ b/include/beast.hpp @@ -0,0 +1,19 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HPP +#define BEAST_HPP + +#include + +#include +#include +#include +#include +#include + +#endif diff --git a/include/beast/config.hpp b/include/beast/config.hpp index b87262c779..9c92df041f 100644 --- a/include/beast/config.hpp +++ b/include/beast/config.hpp @@ -8,17 +8,23 @@ #ifndef BEAST_CONFIG_HPP #define BEAST_CONFIG_HPP +#include + +// Available to every header +#include +#include + /* _MSC_VER and _MSC_FULL_VER by version: - 14.0 (2015) 1900 190023026 - 14.0 (2015 Update 1) 1900 190023506 - 14.0 (2015 Update 2) 1900 190023918 - 14.0 (2015 Update 3) 1900 190024210 + 14.0 (2015) 1900 190023026 + 14.0 (2015 Update 1) 1900 190023506 + 14.0 (2015 Update 2) 1900 190023918 + 14.0 (2015 Update 3) 1900 190024210 */ -#if defined(_MSC_FULL_VER) -#if _MSC_FULL_VER < 190024210 +#ifdef BOOST_MSVC +#if BOOST_MSVC_FULL_VER < 190024210 static_assert(false, "This library requires Visual Studio 2015 Update 3 or later"); #endif diff --git a/include/beast/core.hpp b/include/beast/core.hpp index 9377cd99fe..3084c3e886 100644 --- a/include/beast/core.hpp +++ b/include/beast/core.hpp @@ -10,25 +10,31 @@ #include -#include +#include #include #include -#include +#include +#include #include #include +#include #include +#include +#include +#include +#include +#include +#include #include -#include -#include #include -#include -#include -#include +#include +#include +#include +#include +#include #include -#include -#include -#include -#include -#include +#include +#include +#include #endif diff --git a/include/beast/core/async_completion.hpp b/include/beast/core/async_completion.hpp deleted file mode 100644 index 8c7f64d9ff..0000000000 --- a/include/beast/core/async_completion.hpp +++ /dev/null @@ -1,88 +0,0 @@ -// -// 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_ASYNC_COMPLETION_HPP -#define BEAST_ASYNC_COMPLETION_HPP - -#include -#include -#include -#include -#include -#include - -namespace beast { - -/** Helper for customizing the return type of asynchronous initiation functions. - - This class template is used to transform caller-provided completion - handlers in calls to asynchronous initiation functions. The transformation - allows customization of the return type of the initiating function, and the - function signature of the final handler. - - @tparam CompletionHandler A completion handler, or a user defined type - with specializations for customizing the return type (for example, - `boost::asio::use_future` or `boost::asio::yield_context`). - - @tparam Signature The callable signature of the final completion handler. - - Example: - @code - ... - template - typename async_completion::result_type - async_initfn(..., CompletionHandler&& handler) - { - async_completion completion{handler}; - ... - return completion.result.get(); - } - @endcode - - @note See - Library Foundations For Asynchronous Operations -*/ -template -struct async_completion -{ - /** The type of the final handler called by the asynchronous initiation function. - - Objects of this type will be callable with the specified signature. - */ - using handler_type = - typename boost::asio::handler_type< - CompletionHandler, Signature>::type; - - /// The type of the value returned by the asynchronous initiation function. - using result_type = typename - boost::asio::async_result::type; - - /** Construct the helper. - - @param token The completion handler. Copies will be made as - required. If `CompletionHandler` is movable, it may also be moved. - */ - async_completion(typename std::remove_reference::type& token) - : handler(std::forward(token)) - , result(handler) - { - static_assert(is_CompletionHandler::value, - "Handler requirements not met"); - } - - /// The final completion handler, callable with the specified signature. - handler_type handler; - - /// The return value of the asynchronous initiation function. - boost::asio::async_result result; -}; - -} // beast - -#endif diff --git a/include/beast/core/async_result.hpp b/include/beast/core/async_result.hpp new file mode 100644 index 0000000000..8465ba0bc2 --- /dev/null +++ b/include/beast/core/async_result.hpp @@ -0,0 +1,205 @@ +// +// 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_ASYNC_COMPLETION_HPP +#define BEAST_ASYNC_COMPLETION_HPP + +#include +#include +#include +#include +#include +#include + +namespace beast { + +/** An interface for customising the behaviour of an asynchronous initiation function. + + This class is used for determining: + + @li The concrete completion handler type to be called at the end of the + asynchronous operation; + + @li the initiating function return type; and + + @li how the return value of the initiating function is obtained. + + The trait allows the handler and return types to be determined at the point + where the specific completion handler signature is known. + + This template takes advantage of specializations of both + `boost::asio::async_result` and `boost::asio::handler_type` for user-defined + completion token types. The primary template assumes that the + @b CompletionToken is the completion handler. + + @par Example + + The example shows how to define an asynchronous initiation function + whose completion handler receives an error code: + + @code + template< + class AsyncStream, // A stream supporting asynchronous read and write + class Handler // The handler to call with signature void(error_code) + > + async_return_type< // This provides the return type customization + Handler, void(error_code)> + do_async( + AsyncStream& stream, // The stream to work on + Handler&& handler) // Could be an rvalue or const reference + { + // Make sure we have an async stream + static_assert(is_async_stream::value, + "AsyncStream requirements not met"); + + // This helper converts the handler into the real handler type + async_completion init{handler}; + + ... // Create and invoke the composed operation + + // This provides the return value and executor customization + return init.result.get(); + } + @endcode + + @see @ref async_completion, @ref async_return_type, @ref handler_type +*/ +template +class async_result +{ + BOOST_STATIC_ASSERT( + ! std::is_reference::value); + + boost::asio::async_result::type> impl_; + + async_result(async_result const&) = delete; + async_result& operator=(async_result const&) = delete; + +public: + /// The concrete completion handler type for the specific signature. + using completion_handler_type = + typename boost::asio::handler_type< + CompletionToken, Signature>::type; + + /// The return type of the initiating function. + using return_type = + typename boost::asio::async_result< + completion_handler_type>::type; + + /** Construct an async result from a given handler. + + When using a specalised async_result, the constructor has + an opportunity to initialise some state associated with the + completion handler, which is then returned from the initiating + function. + */ + explicit + async_result(completion_handler_type& h) + : impl_(h) + { + } + + /// Obtain the value to be returned from the initiating function. + return_type + get() + { + return impl_.get(); + } +}; + +/** Helper for customizing the return type of asynchronous initiation functions. + + This class template is used to transform caller-provided completion + handlers in calls to asynchronous initiation functions. The transformation + allows customization of the return type of the initiating function, and the + function signature of the final handler. + + Example: + @code + ... + template + typename async_completion::result_type + async_initfn(..., CompletionToken&& handler) + { + async_completion completion{handler}; + ... + return completion.result.get(); + } + @endcode + + @tparam CompletionToken Specifies the model used to obtain the result of + the asynchronous operation. + + @tparam Signature The call signature for the completion handler type invoked + on completion of the asynchronous operation. + + @note See + Working Draft, C++ Extensions for Networking + + @see @ref async_return_type, @ref handler_type +*/ +template +struct async_completion +{ + /** The type of the final handler called by the asynchronous initiation function. + + Objects of this type will be callable with the specified signature. + */ + using completion_handler_type = typename async_result< + typename std::decay::type, + Signature>::completion_handler_type; + + /** Constructor + + The constructor creates the concrete completion handler and + makes the link between the handler and the asynchronous + result. + + @param token The completion token. If this is a regular completion + handler, copies may be made as needed. If the handler is movable, + it may also be moved. + */ + explicit + async_completion(CompletionToken& token) + : completion_handler(static_cast::value, + completion_handler_type&, CompletionToken&&>::type>(token)) + , result(completion_handler) + { + // CompletionToken is not invokable with the given signature + static_assert(is_completion_handler< + completion_handler_type, Signature>::value, + "CompletionToken requirements not met: signature mismatch"); + } + + /// The final completion handler, callable with the specified signature. + typename std::conditional::value, + completion_handler_type&, + completion_handler_type + >::type completion_handler; + + /// The return value of the asynchronous initiation function. + async_result::type, Signature> result; +}; + +template +using handler_type = typename beast::async_result< + typename std::decay::type, + Signature>::completion_handler_type; + +template +using async_return_type = typename beast::async_result< + typename std::decay::type, + Signature>::return_type; + +} // beast + +#endif diff --git a/include/beast/core/bind_handler.hpp b/include/beast/core/bind_handler.hpp index 1b2d11daf8..a4a30280a6 100644 --- a/include/beast/core/bind_handler.hpp +++ b/include/beast/core/bind_handler.hpp @@ -9,7 +9,7 @@ #define BEAST_BIND_HANDLER_HPP #include -#include +#include #include #include #include @@ -24,24 +24,23 @@ namespace beast { the returned handler, which provides the same `io_service` execution guarantees as the original handler. - Unlike `io_service::wrap`, the returned handler can be used in - a subsequent call to `io_service::post` instead of - `io_service::dispatch`, to ensure that the handler will not be - invoked immediately by the calling function. + Unlike `boost::asio::io_service::wrap`, the returned handler can + be used in a subsequent call to `boost::asio::io_service::post` + instead of `boost::asio::io_service::dispatch`, to ensure that + the handler will not be invoked immediately by the calling + function. Example: @code - template void - do_cancel(AsyncReadStream& stream, ReadHandler&& handler) + signal_aborted(AsyncReadStream& stream, ReadHandler&& handler) { stream.get_io_service().post( bind_handler(std::forward(handler), boost::asio::error::operation_aborted, 0)); } - @endcode @param handler The handler to wrap. @@ -50,7 +49,7 @@ namespace beast { arguments are forwarded into the returned object. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN implementation_defined #else detail::bound_handler< @@ -58,7 +57,7 @@ detail::bound_handler< #endif bind_handler(Handler&& handler, Args&&... args) { - static_assert(is_CompletionHandler< + static_assert(is_completion_handler< Handler, void(Args...)>::value, "Handler requirements not met"); return detail::bound_handler -#include -#include -#include -#include -#include -#include +#include #include -#include namespace beast { +/** A buffer sequence representing a concatenation of buffer sequences. + + @see @ref buffer_cat +*/ +template +class buffer_cat_view +{ +#if 0 + static_assert( + detail::is_all_const_buffer_sequence::value, + "BufferSequence requirements not met"); +#endif + + std::tuple bn_; + +public: + /** The type of buffer returned when dereferencing an iterator. + + If every buffer sequence in the view is a @b MutableBufferSequence, + then `value_type` will be `boost::asio::mutable_buffer`. + Otherwise, `value_type` will be `boost::asio::const_buffer`. + */ + using value_type = + #if BEAST_DOXYGEN + implementation_defined; + #else + typename detail::common_buffers_type::type; + #endif + + /// The type of iterator used by the concatenated sequence + class const_iterator; + + /// Move constructor + buffer_cat_view(buffer_cat_view&&) = default; + + /// Copy constructor + buffer_cat_view(buffer_cat_view const&) = default; + + /// Move assignment + buffer_cat_view& operator=(buffer_cat_view&&) = default; + + // Copy assignment + buffer_cat_view& operator=(buffer_cat_view const&) = default; + + /** Constructor + + @param buffers The list of buffer sequences to concatenate. + Copies of the arguments will be made; however, the ownership + of memory is not transferred. + */ + explicit + buffer_cat_view(Buffers const&... buffers); + + /// Return an iterator to the beginning of the concatenated sequence. + const_iterator + begin() const; + + /// Return an iterator to the end of the concatenated sequence. + const_iterator + end() const; +}; + /** Concatenate 2 or more buffer sequences. This function returns a constant or mutable buffer sequence which, @@ -34,26 +90,30 @@ namespace beast { @return A new buffer sequence that represents the concatenation of the input buffer sequences. This buffer sequence will be a @b MutableBufferSequence if each of the passed buffer sequences is - also a @b MutableBufferSequence, else the returned buffer sequence - will be a @b ConstBufferSequence. + also a @b MutableBufferSequence; otherwise the returned buffer + sequence will be a @b ConstBufferSequence. + + @see @ref buffer_cat_view */ -#if GENERATING_DOCS +#if BEAST_DOXYGEN template -implementation_defined +buffer_cat_view buffer_cat(BufferSequence const&... buffers) #else template -detail::buffer_cat_helper +inline +buffer_cat_view buffer_cat(B1 const& b1, B2 const& b2, Bn const&... bn) #endif { static_assert( - detail::is_all_ConstBufferSequence::value, + detail::is_all_const_buffer_sequence::value, "BufferSequence requirements not met"); - return detail::buffer_cat_helper< - B1, B2, Bn...>{b1, b2, bn...}; + return buffer_cat_view{b1, b2, bn...}; } } // beast +#include + #endif diff --git a/include/beast/core/buffer_concepts.hpp b/include/beast/core/buffer_concepts.hpp deleted file mode 100644 index 545689eb3f..0000000000 --- a/include/beast/core/buffer_concepts.hpp +++ /dev/null @@ -1,62 +0,0 @@ -// -// 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_BUFFER_CONCEPTS_HPP -#define BEAST_BUFFER_CONCEPTS_HPP - -#include -#include -#include -#include - -namespace beast { - -/// Determine if `T` meets the requirements of @b `BufferSequence`. -template -#if GENERATING_DOCS -struct is_BufferSequence : std::integral_constant -#else -struct is_BufferSequence : detail::is_BufferSequence::type -#endif -{ -}; - -/// Determine if `T` meets the requirements of @b `ConstBufferSequence`. -template -#if GENERATING_DOCS -struct is_ConstBufferSequence : std::integral_constant -#else -struct is_ConstBufferSequence : - is_BufferSequence -#endif -{ -}; - -/// Determine if `T` meets the requirements of @b `DynamicBuffer`. -template -#if GENERATING_DOCS -struct is_DynamicBuffer : std::integral_constant -#else -struct is_DynamicBuffer : detail::is_DynamicBuffer::type -#endif -{ -}; - -/// Determine if `T` meets the requirements of @b `MutableBufferSequence`. -template -#if GENERATING_DOCS -struct is_MutableBufferSequence : std::integral_constant -#else -struct is_MutableBufferSequence : - is_BufferSequence -#endif -{ -}; - -} // beast - -#endif diff --git a/include/beast/core/buffer_prefix.hpp b/include/beast/core/buffer_prefix.hpp new file mode 100644 index 0000000000..1450504f78 --- /dev/null +++ b/include/beast/core/buffer_prefix.hpp @@ -0,0 +1,208 @@ +// +// 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_BUFFER_PREFIX_HPP +#define BEAST_BUFFER_PREFIX_HPP + +#include +#include +#include +#include +#include +#include + +namespace beast { + +/** A buffer sequence adapter that shortens the sequence size. + + The class adapts a buffer sequence to efficiently represent + a shorter subset of the original list of buffers starting + with the first byte of the original sequence. + + @tparam BufferSequence The buffer sequence to adapt. +*/ +template +class buffer_prefix_view +{ + using buffers_type = typename + std::decay::type; + + using iter_type = typename buffers_type::const_iterator; + + BufferSequence bs_; + iter_type back_; + iter_type end_; + std::size_t size_; + + template + buffer_prefix_view(Deduced&& other, + std::size_t nback, std::size_t nend) + : bs_(std::forward(other).bs_) + , back_(std::next(bs_.begin(), nback)) + , end_(std::next(bs_.begin(), nend)) + , size_(other.size_) + { + } + + void + setup(std::size_t n); + +public: + /// The type for each element in the list of buffers. + using value_type = typename std::conditional< + std::is_convertible::value_type, + boost::asio::mutable_buffer>::value, + boost::asio::mutable_buffer, + boost::asio::const_buffer>::type; + +#if BEAST_DOXYGEN + /// A bidirectional iterator type that may be used to read elements. + using const_iterator = implementation_defined; + +#else + class const_iterator; + +#endif + + /// Move constructor. + buffer_prefix_view(buffer_prefix_view&&); + + /// Copy constructor. + buffer_prefix_view(buffer_prefix_view const&); + + /// Move assignment. + buffer_prefix_view& operator=(buffer_prefix_view&&); + + /// Copy assignment. + buffer_prefix_view& operator=(buffer_prefix_view const&); + + /** Construct a buffer sequence prefix. + + @param n The maximum number of bytes in the prefix. + If this is larger than the size of passed, buffers, + the resulting sequence will represent the entire + input sequence. + + @param buffers The buffer sequence to adapt. A copy of + the sequence will be made, but ownership of the underlying + memory is not transferred. + */ + buffer_prefix_view(std::size_t n, BufferSequence const& buffers); + + /** Construct a buffer sequence prefix in-place. + + @param n The maximum number of bytes in the prefix. + If this is larger than the size of passed, buffers, + the resulting sequence will represent the entire + input sequence. + + @param args Arguments forwarded to the contained buffers constructor. + */ + template + buffer_prefix_view(std::size_t n, + boost::in_place_init_t, Args&&... args); + + /// Get a bidirectional iterator to the first element. + const_iterator + begin() const; + + /// Get a bidirectional iterator to one past the last element. + const_iterator + end() const; +}; + +/** Returns a prefix of a constant buffer. + + The returned buffer points to the same memory as the + passed buffer, but with a size that is equal to or less + than the size of the original buffer. + + @param n The size of the returned buffer. + + @param buffer The buffer to shorten. The underlying + memory is not modified. + + @return A new buffer that points to the first `n` bytes + of the original buffer. +*/ +inline +boost::asio::const_buffer +buffer_prefix(std::size_t n, + boost::asio::const_buffer buffer) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + return { buffer_cast(buffer), + (std::min)(n, buffer_size(buffer)) }; +} + +/** Returns a prefix of a mutable buffer. + + The returned buffer points to the same memory as the + passed buffer, but with a size that is equal to or less + than the size of the original buffer. + + @param n The size of the returned buffer. + + @param buffer The buffer to shorten. The underlying + memory is not modified. + + @return A new buffer that points to the first `n` bytes + of the original buffer. +*/ +inline +boost::asio::mutable_buffer +buffer_prefix(std::size_t n, + boost::asio::mutable_buffer buffer) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + return { buffer_cast(buffer), + (std::min)(n, buffer_size(buffer)) }; +} + +/** Returns a prefix of a buffer sequence. + + This function returns a new buffer sequence which when iterated, + presents a shorter subset of the original list of buffers starting + with the first byte of the original sequence. + + @param n The maximum number of bytes in the wrapped + sequence. If this is larger than the size of passed, + buffers, the resulting sequence will represent the + entire input sequence. + + @param buffers An instance of @b ConstBufferSequence or + @b MutableBufferSequence to adapt. A copy of the sequence + will be made, but ownership of the underlying memory is + not transferred. +*/ +template +#if BEAST_DOXYGEN +buffer_prefix_view +#else +inline +typename std::enable_if< + ! std::is_same::value && + ! std::is_same::value, + buffer_prefix_view>::type +#endif +buffer_prefix(std::size_t n, BufferSequence const& buffers) +{ + static_assert( + is_const_buffer_sequence::value || + is_mutable_buffer_sequence::value, + "BufferSequence requirements not met"); + return buffer_prefix_view(n, buffers); +} + +} // beast + +#include + +#endif diff --git a/include/beast/core/dynabuf_readstream.hpp b/include/beast/core/buffered_read_stream.hpp similarity index 87% rename from include/beast/core/dynabuf_readstream.hpp rename to include/beast/core/buffered_read_stream.hpp index e914d434d3..a365babe01 100644 --- a/include/beast/core/dynabuf_readstream.hpp +++ b/include/beast/core/buffered_read_stream.hpp @@ -5,16 +5,14 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_DYNABUF_READSTREAM_HPP -#define BEAST_DYNABUF_READSTREAM_HPP +#ifndef BEAST_BUFFERED_READ_STREAM_HPP +#define BEAST_BUFFERED_READ_STREAM_HPP #include -#include -#include +#include #include -#include -#include -#include +#include +#include #include #include #include @@ -22,11 +20,11 @@ namespace beast { -/** A @b `Stream` with attached @b `DynamicBuffer` to buffer reads. +/** A @b Stream with attached @b DynamicBuffer to buffer reads. - This wraps a @b `Stream` implementation so that calls to write are + This wraps a @b Stream implementation so that calls to write are passed through to the underlying stream, while calls to read will - first consume the input sequence stored in a @b `DynamicBuffer` which + first consume the input sequence stored in a @b DynamicBuffer which is part of the object. The use-case for this class is different than that of the @@ -52,7 +50,7 @@ namespace beast { // template void process_http_message( - dynabuf_readstream& stream) + buffered_read_stream& stream) { // Read up to and including the end of the HTTP // header, leaving the sequence in the stream's @@ -64,11 +62,11 @@ namespace beast { boost::asio::read_until( stream.next_layer(), stream.buffer(), "\r\n\r\n"); - // Use prepare_buffers() to limit the input + // Use buffer_prefix() to limit the input // sequence to only the data up to and including // the trailing "\r\n\r\n". // - auto header_buffers = prepare_buffers( + auto header_buffers = buffer_prefix( bytes_transferred, stream.buffer().data()); ... @@ -88,9 +86,9 @@ namespace beast { @tparam DynamicBuffer The type of stream buffer to use. */ template -class dynabuf_readstream +class buffered_read_stream { - static_assert(is_DynamicBuffer::value, + static_assert(is_dynamic_buffer::value, "DynamicBuffer requirements not met"); template @@ -102,7 +100,7 @@ class dynabuf_readstream public: /// The type of the internal buffer - using dynabuf_type = DynamicBuffer; + using buffer_type = DynamicBuffer; /// The type of the next layer. using next_layer_type = @@ -110,26 +108,21 @@ public: /// The type of the lowest layer. using lowest_layer_type = -#if GENERATING_DOCS - implementation_defined; -#else - typename detail::get_lowest_layer< - next_layer_type>::type; -#endif + typename get_lowest_layer::type; /** Move constructor. @note The behavior of move assignment on or from streams with active or pending operations is undefined. */ - dynabuf_readstream(dynabuf_readstream&&) = default; + buffered_read_stream(buffered_read_stream&&) = default; /** Move assignment. @note The behavior of move assignment on or from streams with active or pending operations is undefined. */ - dynabuf_readstream& operator=(dynabuf_readstream&&) = default; + buffered_read_stream& operator=(buffered_read_stream&&) = default; /** Construct the wrapping stream. @@ -137,7 +130,7 @@ public: */ template explicit - dynabuf_readstream(Args&&... args); + buffered_read_stream(Args&&... args); /// Get a reference to the next layer. next_layer_type& @@ -272,10 +265,10 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion::result_type + async_return_type #endif async_read_some(MutableBufferSequence const& buffers, ReadHandler&& handler); @@ -296,7 +289,7 @@ public: std::size_t write_some(ConstBufferSequence const& buffers) { - static_assert(is_SyncWriteStream::value, + static_assert(is_sync_write_stream::value, "SyncWriteStream requirements not met"); return next_layer_.write_some(buffers); } @@ -318,7 +311,7 @@ public: write_some(ConstBufferSequence const& buffers, error_code& ec) { - static_assert(is_SyncWriteStream::value, + static_assert(is_sync_write_stream::value, "SyncWriteStream requirements not met"); return next_layer_.write_some(buffers, ec); } @@ -347,10 +340,10 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion::result_type + async_return_type #endif async_write_some(ConstBufferSequence const& buffers, WriteHandler&& handler); @@ -358,6 +351,6 @@ public: } // beast -#include +#include #endif diff --git a/include/beast/core/buffers_adapter.hpp b/include/beast/core/buffers_adapter.hpp index 36220d0564..6cff9923ca 100644 --- a/include/beast/core/buffers_adapter.hpp +++ b/include/beast/core/buffers_adapter.hpp @@ -9,16 +9,16 @@ #define BEAST_BUFFERS_ADAPTER_HPP #include -#include +#include #include #include namespace beast { -/** Adapts a @b `MutableBufferSequence` into a @b `DynamicBuffer`. +/** Adapts a @b MutableBufferSequence into a @b DynamicBuffer. - This class wraps a @b `MutableBufferSequence` to meet the requirements - of @b `DynamicBuffer`. Upon construction the input and output sequences are + This class wraps a @b MutableBufferSequence to meet the requirements + of @b DynamicBuffer. Upon construction the input and output sequences are empty. A copy of the mutable buffer sequence object is stored; however, ownership of the underlying memory is not transferred. The caller is responsible for making sure that referenced memory remains valid @@ -32,7 +32,7 @@ namespace beast { template class buffers_adapter { - static_assert(is_MutableBufferSequence::value, + static_assert(is_mutable_buffer_sequence::value, "MutableBufferSequence requirements not met"); using iter_type = typename MutableBufferSequence::const_iterator; @@ -64,7 +64,7 @@ class buffers_adapter } public: -#if GENERATING_DOCS +#if BEAST_DOXYGEN /// The type used to represent the input sequence as a list of buffers. using const_buffers_type = implementation_defined; @@ -112,6 +112,13 @@ public: { return in_size_; } + + /// Returns the maximum sum of the sizes of the input sequence and output sequence the buffer can hold without requiring reallocation. + std::size_t + capacity() const + { + return max_size_; + } /** Get a list of buffers that represents the output sequence, with the given size. diff --git a/include/beast/core/consuming_buffers.hpp b/include/beast/core/consuming_buffers.hpp index 4d8d42a641..6baffaa470 100644 --- a/include/beast/core/consuming_buffers.hpp +++ b/include/beast/core/consuming_buffers.hpp @@ -9,8 +9,9 @@ #define BEAST_CONSUMING_BUFFERS_HPP #include -#include +#include #include +#include #include #include #include @@ -40,7 +41,6 @@ class consuming_buffers BufferSequence bs_; iter_type begin_; - iter_type end_; std::size_t skip_ = 0; template @@ -59,7 +59,7 @@ public: `boost::asio::mutable_buffer`, else this type will be `boost::asio::const_buffer`. */ -#if GENERATING_DOCS +#if BEAST_DOXYGEN using value_type = ...; #else using value_type = typename std::conditional< @@ -70,7 +70,7 @@ public: boost::asio::const_buffer>::type; #endif -#if GENERATING_DOCS +#if BEAST_DOXYGEN /// A bidirectional iterator type that may be used to read elements. using const_iterator = implementation_defined; @@ -79,18 +79,15 @@ public: #endif - /// Move constructor. + /// Constructor + consuming_buffers(); + + /// Move constructor consuming_buffers(consuming_buffers&&); - /// Copy constructor. + /// Copy constructor consuming_buffers(consuming_buffers const&); - /// Move assignment. - consuming_buffers& operator=(consuming_buffers&&); - - /// Copy assignment. - consuming_buffers& operator=(consuming_buffers const&); - /** Construct to represent a buffer sequence. A copy of the buffer sequence is made. Ownership of the @@ -99,6 +96,19 @@ public: explicit consuming_buffers(BufferSequence const& buffers); + /** Construct a buffer sequence in-place. + + @param args Arguments forwarded to the contained buffers constructor. + */ + template + consuming_buffers(boost::in_place_init_t, Args&&... args); + + /// Move assignment + consuming_buffers& operator=(consuming_buffers&&); + + /// Copy assignmen + consuming_buffers& operator=(consuming_buffers const&); + /// Get a bidirectional iterator to the first element. const_iterator begin() const; diff --git a/include/beast/core/detail/base64.hpp b/include/beast/core/detail/base64.hpp index 407b79db37..31b426a61e 100644 --- a/include/beast/core/detail/base64.hpp +++ b/include/beast/core/detail/base64.hpp @@ -5,15 +5,6 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_DETAIL_BASE64_HPP -#define BEAST_DETAIL_BASE64_HPP - -#include -#include - -namespace beast { -namespace detail { - /* Portions from http://www.adp-gmbh.ch/cpp/common/base64.html Copyright notice: @@ -44,77 +35,195 @@ namespace detail { */ -template -std::string const& -base64_alphabet() +#ifndef BEAST_DETAIL_BASE64_HPP +#define BEAST_DETAIL_BASE64_HPP + +#include +#include +#include + +namespace beast { +namespace detail { + +namespace base64 { + +inline +char const* +get_alphabet() { - static std::string const alphabet = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; - return alphabet; + static char constexpr tab[] = { + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + }; + return &tab[0]; } inline -bool -is_base64(unsigned char c) +signed char const* +get_inverse() { - return (std::isalnum(c) || (c == '+') || (c == '/')); + static signed char constexpr tab[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0-15 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16-31 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, // 32-47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, // 48-63 + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 64-79 + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, // 80-95 + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 96-111 + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, // 112-127 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 128-143 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 144-159 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 160-175 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 176-191 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 192-207 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 208-223 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 224-239 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // 240-255 + }; + return &tab[0]; } -template -std::string -base64_encode (std::uint8_t const* data, - std::size_t in_len) + +/// Returns max chars needed to encode a base64 string +inline +std::size_t constexpr +encoded_size(std::size_t n) { + return 4 * ((n + 2) / 3); +} + +/// Returns max bytes needed to decode a base64 string +inline +std::size_t constexpr +decoded_size(std::size_t n) +{ + return n / 4 * 3; // requires n&3==0, smaller + //return 3 * n / 4; +} + +/** Encode a series of octets as a padded, base64 string. + + The resulting string will not be null terminated. + + @par Requires + + The memory pointed to by `out` points to valid memory + of at least `encoded_size(len)` bytes. + + @return The number of characters written to `out`. This + will exclude any null termination. +*/ +template +std::size_t +encode(void* dest, void const* src, std::size_t len) +{ + char* out = static_cast(dest); + char const* in = static_cast(src); + auto const tab = base64::get_alphabet(); + + for(auto n = len / 3; n--;) + { + *out++ = tab[ (in[0] & 0xfc) >> 2]; + *out++ = tab[((in[0] & 0x03) << 4) + ((in[1] & 0xf0) >> 4)]; + *out++ = tab[((in[2] & 0xc0) >> 6) + ((in[1] & 0x0f) << 2)]; + *out++ = tab[ in[2] & 0x3f]; + in += 3; + } + + switch(len % 3) + { + case 2: + *out++ = tab[ (in[0] & 0xfc) >> 2]; + *out++ = tab[((in[0] & 0x03) << 4) + ((in[1] & 0xf0) >> 4)]; + *out++ = tab[ (in[1] & 0x0f) << 2]; + *out++ = '='; + break; + + case 1: + *out++ = tab[ (in[0] & 0xfc) >> 2]; + *out++ = tab[((in[0] & 0x03) << 4)]; + *out++ = '='; + *out++ = '='; + break; + + case 0: + break; + } + + return out - static_cast(dest); +} + +/** Decode a padded base64 string into a series of octets. + + @par Requires + + The memory pointed to by `out` points to valid memory + of at least `decoded_size(len)` bytes. + + @return The number of octets written to `out`, and + the number of characters read from the input string, + expressed as a pair. +*/ +template +std::pair +decode(void* dest, char const* src, std::size_t len) +{ + char* out = static_cast(dest); + auto in = reinterpret_cast(src); unsigned char c3[3], c4[4]; int i = 0; int j = 0; - std::string ret; - ret.reserve (3 + in_len * 8 / 6); + auto const inverse = base64::get_inverse(); - char const* alphabet (base64_alphabet().data()); - - while(in_len--) + while(len-- && *in != '=') { - c3[i++] = *(data++); - if(i == 3) + auto const v = inverse[*in]; + if(v == -1) + break; + ++in; + c4[i] = v; + if(++i == 4) { - c4[0] = (c3[0] & 0xfc) >> 2; - c4[1] = ((c3[0] & 0x03) << 4) + ((c3[1] & 0xf0) >> 4); - c4[2] = ((c3[1] & 0x0f) << 2) + ((c3[2] & 0xc0) >> 6); - c4[3] = c3[2] & 0x3f; - for(i = 0; (i < 4); i++) - ret += alphabet[c4[i]]; + c3[0] = (c4[0] << 2) + ((c4[1] & 0x30) >> 4); + c3[1] = ((c4[1] & 0xf) << 4) + ((c4[2] & 0x3c) >> 2); + c3[2] = ((c4[2] & 0x3) << 6) + c4[3]; + + for(i = 0; i < 3; i++) + *out++ = c3[i]; i = 0; } } if(i) { - for(j = i; j < 3; j++) - c3[j] = '\0'; + c3[0] = ( c4[0] << 2) + ((c4[1] & 0x30) >> 4); + c3[1] = ((c4[1] & 0xf) << 4) + ((c4[2] & 0x3c) >> 2); + c3[2] = ((c4[2] & 0x3) << 6) + c4[3]; - c4[0] = (c3[0] & 0xfc) >> 2; - c4[1] = ((c3[0] & 0x03) << 4) + ((c3[1] & 0xf0) >> 4); - c4[2] = ((c3[1] & 0x0f) << 2) + ((c3[2] & 0xc0) >> 6); - c4[3] = c3[2] & 0x3f; - - for(j = 0; (j < i + 1); j++) - ret += alphabet[c4[j]]; - - while((i++ < 3)) - ret += '='; + for(j = 0; j < i - 1; j++) + *out++ = c3[j]; } - return ret; - + return {out - static_cast(dest), + in - reinterpret_cast(src)}; } +} // base64 + template std::string -base64_encode (std::string const& s) +base64_encode (std::uint8_t const* data, + std::size_t len) +{ + std::string dest; + dest.resize(base64::encoded_size(len)); + dest.resize(base64::encode(&dest[0], data, len)); + return dest; +} + +inline +std::string +base64_encode(std::string const& s) { return base64_encode (reinterpret_cast < std::uint8_t const*> (s.data()), s.size()); @@ -124,52 +233,12 @@ template std::string base64_decode(std::string const& data) { - auto in_len = data.size(); - unsigned char c3[3], c4[4]; - int i = 0; - int j = 0; - int in_ = 0; - - std::string ret; - ret.reserve (in_len * 6 / 8); // ??? - - while(in_len-- && (data[in_] != '=') && - is_base64(data[in_])) - { - c4[i++] = data[in_]; in_++; - if(i == 4) { - for(i = 0; i < 4; i++) - c4[i] = static_cast( - base64_alphabet().find(c4[i])); - - c3[0] = (c4[0] << 2) + ((c4[1] & 0x30) >> 4); - c3[1] = ((c4[1] & 0xf) << 4) + ((c4[2] & 0x3c) >> 2); - c3[2] = ((c4[2] & 0x3) << 6) + c4[3]; - - for(i = 0; (i < 3); i++) - ret += c3[i]; - i = 0; - } - } - - if(i) - { - for(j = i; j < 4; j++) - c4[j] = 0; - - for(j = 0; j < 4; j++) - c4[j] = static_cast( - base64_alphabet().find(c4[j])); - - c3[0] = (c4[0] << 2) + ((c4[1] & 0x30) >> 4); - c3[1] = ((c4[1] & 0xf) << 4) + ((c4[2] & 0x3c) >> 2); - c3[2] = ((c4[2] & 0x3) << 6) + c4[3]; - - for(j = 0; (j < i - 1); j++) - ret += c3[j]; - } - - return ret; + std::string dest; + dest.resize(base64::decoded_size(data.size())); + auto const result = base64::decode( + &dest[0], data.data(), data.size()); + dest.resize(result.first); + return dest; } } // detail diff --git a/include/beast/core/detail/bind_handler.hpp b/include/beast/core/detail/bind_handler.hpp index d3cc3cdcea..ad9f4e48c7 100644 --- a/include/beast/core/detail/bind_handler.hpp +++ b/include/beast/core/detail/bind_handler.hpp @@ -8,8 +8,10 @@ #ifndef BEAST_BIND_DETAIL_HANDLER_HPP #define BEAST_BIND_DETAIL_HANDLER_HPP -#include #include +#include +#include +#include #include namespace beast { @@ -68,8 +70,9 @@ public: asio_handler_allocate( std::size_t size, bound_handler* h) { - return beast_asio_helpers:: - allocate(size, h->h_); + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(h->h_)); } friend @@ -77,16 +80,17 @@ public: asio_handler_deallocate( void* p, std::size_t size, bound_handler* h) { - beast_asio_helpers:: - deallocate(p, size, h->h_); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(h->h_)); } friend bool asio_handler_is_continuation(bound_handler* h) { - return beast_asio_helpers:: - is_continuation (h->h_); + using boost::asio::asio_handler_is_continuation; + return asio_handler_is_continuation(std::addressof(h->h_)); } template @@ -94,8 +98,9 @@ public: void asio_handler_invoke(F&& f, bound_handler* h) { - beast_asio_helpers:: - invoke(f, h->h_); + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(h->h_)); } }; diff --git a/include/beast/core/detail/buffer_concepts.hpp b/include/beast/core/detail/buffer_concepts.hpp deleted file mode 100644 index 6391de4c3a..0000000000 --- a/include/beast/core/detail/buffer_concepts.hpp +++ /dev/null @@ -1,180 +0,0 @@ -// -// 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_DETAIL_BUFFER_CONCEPTS_HPP -#define BEAST_DETAIL_BUFFER_CONCEPTS_HPP - -#include -#include -#include - -namespace beast { -namespace detail { - -// Types that meet the requirements, -// for use with std::declval only. -template -struct BufferSequence -{ - using value_type = BufferType; - using const_iterator = BufferType const*; - ~BufferSequence(); - BufferSequence(BufferSequence const&) = default; - const_iterator begin() const noexcept; - const_iterator end() const noexcept; -}; -using ConstBufferSequence = - BufferSequence; -using MutableBufferSequence = - BufferSequence; - -template -class is_BufferSequence -{ - template > - static R check1(int); - template - static std::false_type check1(...); - using type1 = decltype(check1(0)); - - template::iterator_category>> - #else - // workaround: - // boost::asio::detail::consuming_buffers::const_iterator - // is not bidirectional - std::forward_iterator_tag, - typename std::iterator_traits< - typename U::const_iterator>::iterator_category>> - #endif - static R check2(int); - template - static std::false_type check2(...); - using type2 = decltype(check2(0)); - - template().begin()), - typename U::const_iterator>::type> - static R check3(int); - template - static std::false_type check3(...); - using type3 = decltype(check3(0)); - - template().end()), - typename U::const_iterator>::type> - static R check4(int); - template - static std::false_type check4(...); - using type4 = decltype(check4(0)); - -public: - using type = std::integral_constant::value && - std::is_destructible::value && - type1::value && type2::value && - type3::value && type4::value>; -}; - -template -struct is_all_ConstBufferSequence - : std::integral_constant::type::value && - is_all_ConstBufferSequence::value> -{ -}; - -template -struct is_all_ConstBufferSequence - : is_BufferSequence::type -{ -}; - -template -class is_DynamicBuffer -{ - // size() - template().size()), std::size_t>> - static R check1(int); - template - static std::false_type check1(...); - using type1 = decltype(check1(0)); - - // max_size() - template().max_size()), std::size_t>> - static R check2(int); - template - static std::false_type check2(...); - using type2 = decltype(check2(0)); - - // capacity() - template().capacity()), std::size_t>> - static R check3(int); - template - static std::false_type check3(...); - using type3 = decltype(check3(0)); - - // data() - template().data()), - boost::asio::const_buffer>::type::value>> - static R check4(int); - template - static std::false_type check4(...); - using type4 = decltype(check4(0)); - - // prepare() - template().prepare(1)), - boost::asio::mutable_buffer>::type::value>> - static R check5(int); - template - static std::false_type check5(...); - using type5 = decltype(check5(0)); - - // commit() - template().commit(1), std::true_type{})> - static R check6(int); - template - static std::false_type check6(...); - using type6 = decltype(check6(0)); - - // consume - template().consume(1), std::true_type{})> - static R check7(int); - template - static std::false_type check7(...); - using type7 = decltype(check7(0)); - -public: - using type = std::integral_constant; -}; - -} // detail -} // beast - -#endif diff --git a/include/beast/core/detail/buffers_ref.hpp b/include/beast/core/detail/buffers_ref.hpp new file mode 100644 index 0000000000..92fe9f5356 --- /dev/null +++ b/include/beast/core/detail/buffers_ref.hpp @@ -0,0 +1,61 @@ +// +// 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_DETAIL_BUFFERS_REF_HPP +#define BEAST_DETAIL_BUFFERS_REF_HPP + +#include + +namespace beast { +namespace detail { + +// A very lightweight reference to a buffer sequence +template +class buffers_ref +{ + BufferSequence const& buffers_; + +public: + using value_type = + typename BufferSequence::value_type; + + using const_iterator = + typename BufferSequence::const_iterator; + + buffers_ref(buffers_ref const&) = default; + + explicit + buffers_ref(BufferSequence const& buffers) + : buffers_(buffers) + { + } + + const_iterator + begin() const + { + return buffers_.begin(); + } + + const_iterator + end() const + { + return buffers_.end(); + } +}; + +// Return a reference to a buffer sequence +template +buffers_ref +make_buffers_ref(BufferSequence const& buffers) +{ + return buffers_ref(buffers); +} + +} // detail +} // beast + +#endif diff --git a/include/beast/core/detail/ci_char_traits.hpp b/include/beast/core/detail/ci_char_traits.hpp deleted file mode 100644 index 7dfd0c18ce..0000000000 --- a/include/beast/core/detail/ci_char_traits.hpp +++ /dev/null @@ -1,106 +0,0 @@ -// -// 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_DETAIL_CI_CHAR_TRAITS_HPP -#define BEAST_DETAIL_CI_CHAR_TRAITS_HPP - -#include -#include -#include -#include - -namespace beast { -namespace detail { - -inline -char -tolower(char c) -{ - static std::array constexpr tab = {{ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91, 92, 93, 94, 95, - 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, - 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, - 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, - 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, - 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, - 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, - 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, - 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, - 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 - }}; - return static_cast(tab[static_cast(c)]); -} - -template -inline -boost::string_ref -string_helper(const char (&s)[N]) -{ - return boost::string_ref{s, N-1}; -} - -template -inline -T const& -string_helper(T const& t) -{ - return t; -} - -// Case-insensitive less -struct ci_less -{ - static bool const is_transparent = true; - - template - bool - operator()(S1 const& lhs, S2 const& rhs) const noexcept - { - using std::begin; - using std::end; - auto const s1 = string_helper(lhs); - auto const s2 = string_helper(rhs); - return std::lexicographical_compare( - begin(s1), end(s1), begin(s2), end(s2), - [](char lhs, char rhs) - { - return tolower(lhs) < tolower(rhs); - } - ); - } -}; - -// Case-insensitive equal -struct ci_equal_pred -{ - bool - operator()(char c1, char c2) const noexcept - { - return tolower(c1) == tolower(c2); - } -}; - -// Case-insensitive equal -template -bool -ci_equal(S1 const& lhs, S2 const& rhs) -{ - return boost::range::equal( - string_helper(lhs), string_helper(rhs), - ci_equal_pred{}); -} - -} // detail -} // beast - -#endif diff --git a/include/beast/core/detail/config.hpp b/include/beast/core/detail/config.hpp new file mode 100644 index 0000000000..461ca38bf8 --- /dev/null +++ b/include/beast/core/detail/config.hpp @@ -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) +// + +#ifndef BEAST_CORE_DETAIL_CONFIG_HPP +#define BEAST_CORE_DETAIL_CONFIG_HPP + +#include +#include + +#if BOOST_VERSION >= 106500 || ! defined(BOOST_GCC) || BOOST_GCC < 70000 +# define BEAST_FALLTHROUGH BOOST_FALLTHROUGH +#else +# define BEAST_FALLTHROUGH __attribute__((fallthrough)) +#endif + +#endif diff --git a/include/beast/core/detail/cpu_info.hpp b/include/beast/core/detail/cpu_info.hpp new file mode 100644 index 0000000000..3a1e24cb38 --- /dev/null +++ b/include/beast/core/detail/cpu_info.hpp @@ -0,0 +1,95 @@ +// +// 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) +// + +#ifndef BEAST_DETAIL_CPU_INFO_HPP +#define BEAST_DETAIL_CPU_INFO_HPP + +#include + +#ifndef BEAST_NO_INTRINSICS +# if defined(BOOST_MSVC) || ((defined(BOOST_GCC) || defined(BOOST_CLANG)) && defined(__SSE4_2__)) +# define BEAST_NO_INTRINSICS 0 +# else +# define BEAST_NO_INTRINSICS 1 +# endif +#endif + +#if ! BEAST_NO_INTRINSICS + +#ifdef BOOST_MSVC +#include // __cpuid +#else +#include // __get_cpuid +#endif + +namespace beast { +namespace detail { + +/* Portions from Boost, + Copyright Andrey Semashev 2007 - 2015. +*/ +template +void +cpuid( + std::uint32_t id, + std::uint32_t& eax, + std::uint32_t& ebx, + std::uint32_t& ecx, + std::uint32_t& edx) +{ +#ifdef BOOST_MSVC + int regs[4]; + __cpuid(regs, id); + eax = regs[0]; + ebx = regs[1]; + ecx = regs[2]; + edx = regs[3]; +#else + __get_cpuid(id, &eax, &ebx, &ecx, &edx); +#endif +} + +struct cpu_info +{ + bool sse42 = false; + + cpu_info(); +}; + +inline +cpu_info:: +cpu_info() +{ + constexpr std::uint32_t SSE42 = 1 << 20; + + std::uint32_t eax = 0; + std::uint32_t ebx = 0; + std::uint32_t ecx = 0; + std::uint32_t edx = 0; + + cpuid(0, eax, ebx, ecx, edx); + if(eax >= 1) + { + cpuid(1, eax, ebx, ecx, edx); + sse42 = (ecx & SSE42) != 0; + } +} + +template +cpu_info const& +get_cpu_info() +{ + static cpu_info const ci; + return ci; +} + +} // detail +} // beast + +#endif + +#endif diff --git a/include/beast/core/detail/get_lowest_layer.hpp b/include/beast/core/detail/get_lowest_layer.hpp deleted file mode 100644 index 4b199ce665..0000000000 --- a/include/beast/core/detail/get_lowest_layer.hpp +++ /dev/null @@ -1,53 +0,0 @@ -// -// 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_DETAIL_GET_LOWEST_LAYER_HPP -#define BEAST_DETAIL_GET_LOWEST_LAYER_HPP - -#include - -namespace beast { -namespace detail { - -template -class has_lowest_layer -{ - template - static std::true_type check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); -public: - static bool constexpr value = type::value; -}; - -template -struct maybe_get_lowest_layer -{ - using type = T; -}; - -template -struct maybe_get_lowest_layer -{ - using type = typename T::lowest_layer_type; -}; - -// If T has a nested type lowest_layer_type, -// returns that, else returns T. -template -struct get_lowest_layer -{ - using type = typename maybe_get_lowest_layer::value>::type; -}; - -} // detail -} // beast - -#endif diff --git a/include/beast/core/detail/in_place_init.hpp b/include/beast/core/detail/in_place_init.hpp new file mode 100644 index 0000000000..2a0ab615f9 --- /dev/null +++ b/include/beast/core/detail/in_place_init.hpp @@ -0,0 +1,41 @@ +// +// 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_DETAIL_IN_PLACE_INIT_HPP +#define BEAST_DETAIL_IN_PLACE_INIT_HPP + +#include +#include + +// Provide boost::in_place_init_t and boost::in_place_init +// for Boost versions earlier than 1.63.0. + +#if BOOST_VERSION < 106300 + +namespace boost { + +namespace optional_ns { + +// a tag for in-place initialization of contained value +struct in_place_init_t +{ + struct init_tag{}; + explicit in_place_init_t(init_tag){} +}; +const in_place_init_t in_place_init ((in_place_init_t::init_tag())); + +} // namespace optional_ns + +using optional_ns::in_place_init_t; +using optional_ns::in_place_init; + +} + +#endif + +#endif + diff --git a/include/beast/core/detail/integer_sequence.hpp b/include/beast/core/detail/integer_sequence.hpp index 3a2fa418da..6d0cc8a7f3 100644 --- a/include/beast/core/detail/integer_sequence.hpp +++ b/include/beast/core/detail/integer_sequence.hpp @@ -5,9 +5,10 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_DETAIL_INTEGER_SEQUENCE_H_INCLUDED -#define BEAST_DETAIL_INTEGER_SEQUENCE_H_INCLUDED +#ifndef BEAST_DETAIL_INTEGER_SEQUENCE_HPP +#define BEAST_DETAIL_INTEGER_SEQUENCE_HPP +#include #include #include #include @@ -19,8 +20,7 @@ template struct integer_sequence { using value_type = T; - static_assert (std::is_integral::value, - "std::integer_sequence can only be instantiated with an integral type" ); + BOOST_STATIC_ASSERT(std::is_integral::value); static std::size_t constexpr static_size = sizeof...(Ints); @@ -40,9 +40,9 @@ struct sizeof_workaround static std::size_t constexpr size = sizeof... (Args); }; -#ifdef _MSC_VER +#ifdef BOOST_MSVC -// This implementation compiles on MSVC and clang but not gcc +// This implementation compiles on real MSVC and clang but not gcc template struct make_integer_sequence_unchecked; @@ -65,11 +65,8 @@ struct make_integer_sequence_unchecked< template struct make_integer_sequence_checked { - static_assert (std::is_integral::value, - "T must be an integral type"); - - static_assert (N >= 0, - "N must be non-negative"); + BOOST_STATIC_ASSERT(std::is_integral::value); + BOOST_STATIC_ASSERT(N >= 0); using type = typename make_integer_sequence_unchecked< T, N, integer_sequence>::type; @@ -117,11 +114,8 @@ struct integer_sequence_helper; template struct integer_sequence_helper> { - static_assert (std::is_integral::value, - "T must be an integral type"); - - static_assert (N >= 0, - "N must be non-negative"); + BOOST_STATIC_ASSERT(std::is_integral::value); + BOOST_STATIC_ASSERT(N >= 0); using type = integer_sequence (Ints)...>; }; diff --git a/include/beast/core/detail/is_call_possible.hpp b/include/beast/core/detail/is_call_possible.hpp deleted file mode 100644 index bafc2a33d4..0000000000 --- a/include/beast/core/detail/is_call_possible.hpp +++ /dev/null @@ -1,54 +0,0 @@ -// -// 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_DETAIL_IS_CALL_POSSIBLE_HPP -#define BEAST_DETAIL_IS_CALL_POSSIBLE_HPP - -#include - -namespace beast { -namespace detail { - -template -auto -is_call_possible_test(C&& c, int, A&& ...a) - -> decltype(std::is_convertible< - decltype(c(a...)), R>::value || - std::is_same::value, - std::true_type()); - -template -std::false_type -is_call_possible_test(C&& c, long, A&& ...a); - -/** Metafunction returns `true` if F callable as R(A...) - - Example: - - @code - is_call_possible - @endcode -*/ -/** @{ */ -template -struct is_call_possible - : std::false_type -{ -}; - -template -struct is_call_possible - : decltype(is_call_possible_test( - std::declval(), 1, std::declval()...)) -{ -}; -/** @} */ - -} // detail -} // beast - -#endif diff --git a/include/beast/core/detail/ostream.hpp b/include/beast/core/detail/ostream.hpp new file mode 100644 index 0000000000..c02211246d --- /dev/null +++ b/include/beast/core/detail/ostream.hpp @@ -0,0 +1,318 @@ +// +// 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_DETAIL_OSTREAM_HPP +#define BEAST_DETAIL_OSTREAM_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace detail { + +template +class buffers_helper +{ + Buffers b_; + +public: + explicit + buffers_helper(Buffers const& b) + : b_(b) + { + } + + template + friend + std::ostream& + operator<<(std::ostream& os, + buffers_helper const& v); +}; + +template +std::ostream& +operator<<(std::ostream& os, + buffers_helper const& v) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + for(boost::asio::const_buffer b : v.b_) + os.write(buffer_cast(b), + buffer_size(b)); + return os; +} + +//------------------------------------------------------------------------------ + +struct basic_streambuf_movable_helper : + std::basic_streambuf> +{ + basic_streambuf_movable_helper( + basic_streambuf_movable_helper&&) = default; +}; + +using basic_streambuf_movable = + std::is_move_constructible; + +//------------------------------------------------------------------------------ + +template +class ostream_buffer; + +template +class ostream_buffer + + : public std::basic_streambuf +{ + using int_type = typename + std::basic_streambuf::int_type; + + using traits_type = typename + std::basic_streambuf::traits_type; + + static std::size_t constexpr max_size = 512; + + DynamicBuffer& buf_; + +public: + ostream_buffer(ostream_buffer&&) = default; + ostream_buffer(ostream_buffer const&) = delete; + + ~ostream_buffer() noexcept + { + sync(); + } + + explicit + ostream_buffer(DynamicBuffer& buf) + : buf_(buf) + { + prepare(); + } + + int_type + overflow(int_type ch) override + { + if(! Traits::eq_int_type(ch, Traits::eof())) + { + Traits::assign(*this->pptr(), + static_cast(ch)); + flush(1); + prepare(); + return ch; + } + flush(); + return traits_type::eof(); + } + + int + sync() override + { + flush(); + prepare(); + return 0; + } + +private: + void + prepare() + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + auto mbs = buf_.prepare( + read_size_or_throw(buf_, max_size)); + auto const mb = *mbs.begin(); + auto const p = buffer_cast(mb); + this->setp(p, + p + buffer_size(mb) / sizeof(CharT) - 1); + } + + void + flush(int extra = 0) + { + buf_.commit( + (this->pptr() - this->pbase() + extra) * + sizeof(CharT)); + } +}; + +// This nonsense is all to work around a glitch in libstdc++ +// where std::basic_streambuf copy constructor is private: +// https://github.com/gcc-mirror/gcc/blob/gcc-4_8-branch/libstdc%2B%2B-v3/include/std/streambuf#L799 + +template +class ostream_buffer + + : public std::basic_streambuf +{ + using int_type = typename + std::basic_streambuf::int_type; + + using traits_type = typename + std::basic_streambuf::traits_type; + + static std::size_t constexpr max_size = 512; + + DynamicBuffer& buf_; + +public: + ostream_buffer(ostream_buffer&&) = delete; + ostream_buffer(ostream_buffer const&) = delete; + + ~ostream_buffer() noexcept + { + sync(); + } + + explicit + ostream_buffer(DynamicBuffer& buf) + : buf_(buf) + { + prepare(); + } + + int_type + overflow(int_type ch) override + { + if(! Traits::eq_int_type(ch, Traits::eof())) + { + Traits::assign(*this->pptr(), + static_cast(ch)); + flush(1); + prepare(); + return ch; + } + flush(); + return traits_type::eof(); + } + + int + sync() override + { + flush(); + prepare(); + return 0; + } + +private: + void + prepare() + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + auto mbs = buf_.prepare( + read_size_or_throw(buf_, max_size)); + auto const mb = *mbs.begin(); + auto const p = buffer_cast(mb); + this->setp(p, + p + buffer_size(mb) / sizeof(CharT) - 1); + } + + void + flush(int extra = 0) + { + buf_.commit( + (this->pptr() - this->pbase() + extra) * + sizeof(CharT)); + } +}; + +//------------------------------------------------------------------------------ + +template +class ostream_helper; + +template +class ostream_helper< + DynamicBuffer, CharT, Traits, true> + : public std::basic_ostream +{ + ostream_buffer< + DynamicBuffer, CharT, Traits, true> osb_; + +public: + explicit + ostream_helper(DynamicBuffer& buf); + + ostream_helper(ostream_helper&& other); +}; + +template +ostream_helper:: +ostream_helper(DynamicBuffer& buf) + : std::basic_ostream( + &this->osb_) + , osb_(buf) +{ +} + +template +ostream_helper:: +ostream_helper( + ostream_helper&& other) + : std::basic_ostream(&osb_) + , osb_(std::move(other.osb_)) +{ +} + +// This work-around is for libstdc++ versions that +// don't have a movable std::basic_streambuf + +template +class ostream_helper_base +{ +protected: + std::unique_ptr member; + + ostream_helper_base( + ostream_helper_base&&) = default; + + explicit + ostream_helper_base(T* t) + : member(t) + { + } +}; + +template +class ostream_helper< + DynamicBuffer, CharT, Traits, false> + : private ostream_helper_base> + , public std::basic_ostream +{ +public: + explicit + ostream_helper(DynamicBuffer& buf) + : ostream_helper_base>( + new ostream_buffer(buf)) + , std::basic_ostream(this->member.get()) + { + } + + ostream_helper(ostream_helper&& other) + : ostream_helper_base>( + std::move(other)) + , std::basic_ostream(this->member.get()) + { + } +}; + +} // detail +} // beast + +#endif diff --git a/include/beast/core/detail/static_ostream.hpp b/include/beast/core/detail/static_ostream.hpp new file mode 100644 index 0000000000..99f4282d91 --- /dev/null +++ b/include/beast/core/detail/static_ostream.hpp @@ -0,0 +1,138 @@ +// +// 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_DETAIL_STATIC_OSTREAM_HPP +#define BEAST_DETAIL_STATIC_OSTREAM_HPP + +#include +#include +#include + +namespace beast { +namespace detail { + +// http://www.mr-edd.co.uk/blog/beginners_guide_streambuf + +class static_ostream_buffer + : public std::basic_streambuf +{ + using CharT = char; + using Traits = std::char_traits; + using int_type = typename + std::basic_streambuf::int_type; + using traits_type = typename + std::basic_streambuf::traits_type; + + char* data_; + std::size_t size_; + std::size_t len_ = 0; + std::string s_; + +public: + static_ostream_buffer(static_ostream_buffer&&) = delete; + static_ostream_buffer(static_ostream_buffer const&) = delete; + + static_ostream_buffer(char* data, std::size_t size) + : data_(data) + , size_(size) + { + this->setp(data_, data_ + size - 1); + } + + ~static_ostream_buffer() noexcept + { + } + + string_view + str() const + { + if(! s_.empty()) + return {s_.data(), len_}; + return {data_, len_}; + } + + int_type + overflow(int_type ch) override + { + if(! Traits::eq_int_type(ch, Traits::eof())) + { + Traits::assign(*this->pptr(), + static_cast(ch)); + flush(1); + prepare(); + return ch; + } + flush(); + return traits_type::eof(); + } + + int + sync() override + { + flush(); + prepare(); + return 0; + } + +private: + void + prepare() + { + static auto const growth_factor = 1.5; + + if(len_ < size_ - 1) + { + this->setp( + data_ + len_, data_ + size_ - 2); + return; + } + if(s_.empty()) + { + s_.resize(static_cast( + growth_factor * len_)); + Traits::copy(&s_[0], data_, len_); + } + else + { + s_.resize(static_cast( + growth_factor * len_)); + } + this->setp(&s_[len_], &s_[len_] + + s_.size() - len_ - 1); + } + + void + flush(int extra = 0) + { + len_ += static_cast( + this->pptr() - this->pbase() + extra); + } +}; + +class static_ostream : public std::basic_ostream +{ + static_ostream_buffer osb_; + +public: + static_ostream(char* data, std::size_t size) + : std::basic_ostream(&this->osb_) + , osb_(data, size) + { + imbue(std::locale::classic()); + } + + string_view + str() const + { + return osb_.str(); + } +}; + +} // detail +} // beast + +#endif diff --git a/include/beast/core/detail/static_string.hpp b/include/beast/core/detail/static_string.hpp new file mode 100644 index 0000000000..7ded7fff0f --- /dev/null +++ b/include/beast/core/detail/static_string.hpp @@ -0,0 +1,131 @@ +// +// 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_DETAIL_STATIC_STRING_HPP +#define BEAST_DETAIL_STATIC_STRING_HPP + +#include +#include +#include +#include + +namespace beast { +namespace detail { + +// Because k-ballo said so +template +using is_input_iterator = + std::integral_constant::value>; + +template +int +lexicographical_compare( + CharT const* s1, std::size_t n1, + CharT const* s2, std::size_t n2) +{ + if(n1 < n2) + return Traits::compare( + s1, s2, n1) <= 0 ? -1 : 1; + if(n1 > n2) + return Traits::compare( + s1, s2, n2) >= 0 ? 1 : -1; + return Traits::compare(s1, s2, n1); +} + +template +inline +int +lexicographical_compare( + basic_string_view s1, + CharT const* s2, std::size_t n2) +{ + return lexicographical_compare( + s1.data(), s1.size(), s2, n2); +} + +template +inline +int +lexicographical_compare( + basic_string_view s1, + basic_string_view s2) +{ + return lexicographical_compare( + s1.data(), s1.size(), s2.data(), s2.size()); +} + +// Maximum number of characters in the decimal +// representation of a binary number. This includes +// the potential minus sign. +// +inline +std::size_t constexpr +max_digits(std::size_t bytes) +{ + return static_cast( + bytes * 2.41) + 1 + 1; +} + +template +CharT* +raw_to_string( + CharT* buf, Integer x, std::true_type) +{ + if(x == 0) + { + Traits::assign(*--buf, '0'); + return buf; + } + if(x < 0) + { + x = -x; + for(;x > 0; x /= 10) + Traits::assign(*--buf , + "0123456789"[x % 10]); + Traits::assign(*--buf, '-'); + return buf; + } + for(;x > 0; x /= 10) + Traits::assign(*--buf , + "0123456789"[x % 10]); + return buf; +} + +template +CharT* +raw_to_string( + CharT* buf, Integer x, std::false_type) +{ + if(x == 0) + { + *--buf = '0'; + return buf; + } + for(;x > 0; x /= 10) + Traits::assign(*--buf , + "0123456789"[x % 10]); + return buf; +} + +template< + class CharT, + class Integer, + class Traits = std::char_traits> +CharT* +raw_to_string(CharT* last, std::size_t size, Integer i) +{ + boost::ignore_unused(size); + BOOST_ASSERT(size >= max_digits(sizeof(Integer))); + return raw_to_string( + last, i, std::is_signed{}); +} + +} // detail +} // beast + +#endif diff --git a/include/beast/core/detail/stream_concepts.hpp b/include/beast/core/detail/stream_concepts.hpp deleted file mode 100644 index c51991eb06..0000000000 --- a/include/beast/core/detail/stream_concepts.hpp +++ /dev/null @@ -1,134 +0,0 @@ -// -// 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_DETAIL_STREAM_CONCEPTS_HPP -#define BEAST_DETAIL_STREAM_CONCEPTS_HPP - -#include -#include -#include -#include -#include - -namespace beast { -namespace detail { - -// Types that meet the requirements, -// for use with std::declval only. -struct StreamHandler -{ - StreamHandler(StreamHandler const&) = default; - void operator()(error_code ec, std::size_t); -}; -using ReadHandler = StreamHandler; -using WriteHandler = StreamHandler; - -template -class has_get_io_service -{ - template().get_io_service()), - boost::asio::io_service&>> - static R check(int); - template - static std::false_type check(...); -public: - using type = decltype(check(0)); -}; - -template -class is_AsyncReadStream -{ - template().async_read_some( - std::declval(), - std::declval()), - std::true_type{})> - static R check(int); - template - static std::false_type check(...); - using type1 = decltype(check(0)); -public: - using type = std::integral_constant::type::value>; -}; - -template -class is_AsyncWriteStream -{ - template().async_write_some( - std::declval(), - std::declval()), - std::true_type{})> - static R check(int); - template - static std::false_type check(...); - using type1 = decltype(check(0)); -public: - using type = std::integral_constant::type::value>; -}; - -template -class is_SyncReadStream -{ - template().read_some( - std::declval())), - std::size_t>> - static R check1(int); - template - static std::false_type check1(...); - using type1 = decltype(check1(0)); - - template().read_some( - std::declval(), - std::declval())), std::size_t>> - static R check2(int); - template - static std::false_type check2(...); - using type2 = decltype(check2(0)); - -public: - using type = std::integral_constant; -}; - -template -class is_SyncWriteStream -{ - template().write_some( - std::declval())), - std::size_t>> - static R check1(int); - template - static std::false_type check1(...); - using type1 = decltype(check1(0)); - - template().write_some( - std::declval(), - std::declval())), std::size_t>> - static R check2(int); - template - static std::false_type check2(...); - using type2 = decltype(check2(0)); - -public: - using type = std::integral_constant; -}; - -} // detail -} // beast - -#endif diff --git a/include/beast/core/detail/sync_ostream.hpp b/include/beast/core/detail/sync_ostream.hpp deleted file mode 100644 index 5a4e0b47a2..0000000000 --- a/include/beast/core/detail/sync_ostream.hpp +++ /dev/null @@ -1,93 +0,0 @@ -// -// 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_DETAIL_SYNC_OSTREAM_HPP -#define BEAST_DETAIL_SYNC_OSTREAM_HPP - -#include -#include -#include -#include - -namespace beast { -namespace detail { - -/** A SyncWriteStream which outputs to a `std::ostream` - - Objects of this type meet the requirements of @b SyncWriteStream. -*/ -class sync_ostream -{ - std::ostream& os_; - -public: - /** Construct the stream. - - @param os The associated `std::ostream`. All buffers - written will be passed to the associated output stream. - */ - sync_ostream(std::ostream& os) - : os_(os) - { - } - - template - std::size_t - write_some(ConstBufferSequence const& buffers); - - template - std::size_t - write_some(ConstBufferSequence const& buffers, - error_code& ec); -}; - -template -std::size_t -sync_ostream:: -write_some(ConstBufferSequence const& buffers) -{ - static_assert( - is_ConstBufferSequence::value, - "ConstBufferSequence requirements not met"); - error_code ec; - auto const n = write_some(buffers, ec); - if(ec) - throw system_error{ec}; - return n; -} - -template -std::size_t -sync_ostream:: -write_some(ConstBufferSequence const& buffers, - error_code& ec) -{ - static_assert( - is_ConstBufferSequence::value, - "ConstBufferSequence requirements not met"); - std::size_t n = 0; - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - for(auto const& buffer : buffers) - { - os_.write(buffer_cast(buffer), - buffer_size(buffer)); - if(os_.fail()) - { - ec = errc::make_error_code( - errc::no_stream_resources); - break; - } - n += buffer_size(buffer); - } - return n; -} - -} // detail -} // beast - -#endif diff --git a/include/beast/core/detail/type_traits.hpp b/include/beast/core/detail/type_traits.hpp index 4b6acbd445..e375b976d8 100644 --- a/include/beast/core/detail/type_traits.hpp +++ b/include/beast/core/detail/type_traits.hpp @@ -8,14 +8,46 @@ #ifndef BEAST_DETAIL_TYPE_TRAITS_HPP #define BEAST_DETAIL_TYPE_TRAITS_HPP +#include +#include +#include +#include #include #include -#include #include +// A few workarounds to keep things working + +namespace boost { +namespace asio { + +// for has_get_io_service +class io_service; + +// for is_dynamic_buffer +template +class basic_streambuf; + +namespace detail { + +// for is_buffer_sequence +template +class consuming_buffers; + +} // detail + +} // asio +} // boost + +//------------------------------------------------------------------------------ + namespace beast { namespace detail { +// +// utilities +// + template struct make_void { @@ -25,18 +57,10 @@ struct make_void template using void_t = typename make_void::type; -template +template inline void -ignore_unused(Ts const& ...) -{ -} - -template -inline -void -ignore_unused() -{} +accept_rv(T){} template std::size_t constexpr @@ -80,17 +104,159 @@ struct repeat_tuple<0, T> using type = std::tuple<>; }; -template -Exception -make_exception(char const* reason, char const* file, int line) +template +auto +is_invocable_test(C&& c, int, A&& ...a) + -> decltype(std::is_convertible< + decltype(c(a...)), R>::value || + std::is_same::value, + std::true_type()); + +template +std::false_type +is_invocable_test(C&& c, long, A&& ...a); + +/** Metafunction returns `true` if F callable as R(A...) + + Example: + + @code + is_invocable + @endcode +*/ +/** @{ */ +template +struct is_invocable : std::false_type { - char const* n = file; - for(auto p = file; *p; ++p) - if(*p == '\\' || *p == '/') - n = p + 1; - return Exception{std::string(reason) + " (" + - n + ":" + std::to_string(line) + ")"}; -} +}; + +template +struct is_invocable + : decltype(is_invocable_test( + std::declval(), 1, std::declval()...)) +{ +}; +/** @} */ + +// for span +template +struct is_contiguous_container: std::false_type {}; + +template +struct is_contiguous_container() = std::declval().size(), + std::declval() = std::declval().data(), + (void)0), + typename std::enable_if< + std::is_same< + typename std::remove_cv::type, + typename std::remove_cv< + typename std::remove_pointer< + decltype(std::declval().data()) + >::type + >::type + >::value + >::type>>: std::true_type +{}; + +//------------------------------------------------------------------------------ + +// +// buffer concepts +// + +// Types that meet the requirements, +// for use with std::declval only. +template +struct BufferSequence +{ + using value_type = BufferType; + using const_iterator = BufferType const*; + ~BufferSequence(); + BufferSequence(BufferSequence const&) = default; + const_iterator begin() const noexcept; + const_iterator end() const noexcept; +}; +using ConstBufferSequence = + BufferSequence; +using MutableBufferSequence = + BufferSequence; + +template +struct is_buffer_sequence : std::false_type {}; + +template +struct is_buffer_sequence(), + std::declval() = + std::declval().begin(), + std::declval() = + std::declval().end(), + (void)0)>> : std::integral_constant::value && +#if 0 + std::is_base_of::iterator_category>::value +#else + // workaround: + // boost::asio::detail::consuming_buffers::const_iterator + // is not bidirectional + std::is_base_of::iterator_category>::value +#endif + > +{ +}; + +#if 0 +// workaround: +// boost::asio::detail::consuming_buffers::const_iterator +// is not bidirectional +template +struct is_buffer_sequence< + boost::asio::detail::consuming_buffers> + : std::true_type +{ +}; +#endif + +template +struct is_all_const_buffer_sequence + : std::integral_constant::value && + is_all_const_buffer_sequence::value> +{ +}; + +template +struct is_all_const_buffer_sequence + : is_buffer_sequence +{ +}; + +template +struct common_buffers_type +{ + using type = typename std::conditional< + std::is_convertible, + typename repeat_tuple::type>::value, + boost::asio::mutable_buffer, + boost::asio::const_buffer>::type; +}; + +// Types that meet the requirements, +// for use with std::declval only. +struct StreamHandler +{ + StreamHandler(StreamHandler const&) = default; + void operator()(error_code ec, std::size_t); +}; +using ReadHandler = StreamHandler; +using WriteHandler = StreamHandler; } // detail } // beast diff --git a/include/beast/core/detail/write_dynabuf.hpp b/include/beast/core/detail/write_dynabuf.hpp deleted file mode 100644 index dcef73afc0..0000000000 --- a/include/beast/core/detail/write_dynabuf.hpp +++ /dev/null @@ -1,140 +0,0 @@ -// -// 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_DETAIL_WRITE_DYNABUF_HPP -#define BEAST_DETAIL_WRITE_DYNABUF_HPP - -#include -#include -#include -#include - -namespace beast { -namespace detail { - -// detects string literals. -template -struct is_string_literal : std::integral_constant::type>::value && - std::is_same::type>::value> -{ -}; - -// `true` if a call to boost::asio::buffer(T const&) is possible -// note: we exclude string literals because boost::asio::buffer() -// will include the null terminator, which we don't want. -template -class is_BufferConvertible -{ - template()), - std::true_type{})> - static R check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); -public: - static bool const value = type::value && - ! is_string_literal::value; -}; - -template -void -write_dynabuf(DynamicBuffer& dynabuf, - boost::asio::const_buffer const& buffer) -{ - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - dynabuf.commit(buffer_copy( - dynabuf.prepare(buffer_size(buffer)), - buffer)); -} - -template -void -write_dynabuf(DynamicBuffer& dynabuf, - boost::asio::mutable_buffer const& buffer) -{ - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - dynabuf.commit(buffer_copy( - dynabuf.prepare(buffer_size(buffer)), - buffer)); -} - -template -typename std::enable_if< - is_BufferConvertible::value && - ! std::is_convertible::value && - ! std::is_convertible::value ->::type -write_dynabuf(DynamicBuffer& dynabuf, T const& t) -{ - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - auto const buffers = boost::asio::buffer(t); - dynabuf.commit(buffer_copy( - dynabuf.prepare(buffer_size(buffers)), - buffers)); -} - -template -typename std::enable_if< - is_ConstBufferSequence::value && - ! is_BufferConvertible::value && - ! std::is_convertible::value && - ! std::is_convertible::value ->::type -write_dynabuf(DynamicBuffer& dynabuf, Buffers const& buffers) -{ - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - dynabuf.commit(buffer_copy( - dynabuf.prepare(buffer_size(buffers)), - buffers)); -} - -template -void -write_dynabuf(DynamicBuffer& dynabuf, const char (&s)[N]) -{ - using boost::asio::buffer_copy; - dynabuf.commit(buffer_copy( - dynabuf.prepare(N - 1), - boost::asio::buffer(s, N - 1))); -} - -template -typename std::enable_if< - ! is_string_literal::value && - ! is_ConstBufferSequence::value && - ! is_BufferConvertible::value && - ! std::is_convertible::value && - ! std::is_convertible::value ->::type -write_dynabuf(DynamicBuffer& dynabuf, T const& t) -{ - using boost::asio::buffer; - using boost::asio::buffer_copy; - auto const s = boost::lexical_cast(t); - dynabuf.commit(buffer_copy( - dynabuf.prepare(s.size()), buffer(s))); -} - -template -void -write_dynabuf(DynamicBuffer& dynabuf, - T0 const& t0, T1 const& t1, TN const&... tn) -{ - write_dynabuf(dynabuf, t0); - write_dynabuf(dynabuf, t1, tn...); -} - -} // detail -} // beast - -#endif diff --git a/include/beast/core/drain_buffer.hpp b/include/beast/core/drain_buffer.hpp new file mode 100644 index 0000000000..15f89a694f --- /dev/null +++ b/include/beast/core/drain_buffer.hpp @@ -0,0 +1,122 @@ +// +// 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_DRAIN_BUFFER_HPP +#define BEAST_DRAIN_BUFFER_HPP + +#include +#include +#include + +namespace beast { + +/** A @b DynamicBuffer which does not retain its input sequence. + + This object implements a dynamic buffer with a fixed size + output area, not dynamically allocated, and whose input + sequence is always length zero. Bytes committed from the + output area to the input area are always discarded. This + is useful for calling interfaces that require a dynamic + buffer for storage, but where the caller does not want + to retain the data. +*/ +class drain_buffer +{ + char buf_[512]; + std::size_t n_ = 0; + +public: + /// The type used to represent the input sequence as a list of buffers. + using const_buffers_type = boost::asio::null_buffers; + + /// The type used to represent the output sequence as a list of buffers. + using mutable_buffers_type = boost::asio::mutable_buffers_1; + + /// Constructor + drain_buffer() = default; + + /// Copy constructor + drain_buffer(drain_buffer const&) + { + // Previously returned ranges are invalidated + } + + /// Copy assignment + drain_buffer& + operator=(drain_buffer const&) + { + n_ = 0; + return *this; + } + + /// Return the size of the input sequence. + std::size_t + size() const + { + return 0; + } + + /// Return the maximum sum of the input and output sequence sizes. + std::size_t + max_size() const + { + return sizeof(buf_); + } + + /// Return the maximum sum of input and output sizes that can be held without an allocation. + std::size_t + capacity() const + { + return max_size(); + } + + /** Get a list of buffers that represent the input sequence. + + @note These buffers remain valid across subsequent calls to `prepare`. + */ + const_buffers_type + data() const + { + return {}; + } + + /** Get a list of buffers that represent the output sequence, with the given size. + + @throws std::length_error if the size would exceed the buffer limit + */ + mutable_buffers_type + prepare(std::size_t n) + { + if(n > sizeof(buf_)) + BOOST_THROW_EXCEPTION(std::length_error{ + "buffer overflow"}); + n_ = n; + return {buf_, n_}; + } + + /** Move bytes from the output sequence to the input sequence. + + This call always discards the output sequence. + The size of the input sequence will remain at zero. + */ + void + commit(std::size_t) + { + } + + /** Remove bytes from the input sequence. + + This call has no effect. + */ + void + consume(std::size_t) const + { + } +}; +} // beast + +#endif diff --git a/include/beast/core/error.hpp b/include/beast/core/error.hpp index b8c78687c0..f98f6d7af6 100644 --- a/include/beast/core/error.hpp +++ b/include/beast/core/error.hpp @@ -23,8 +23,16 @@ using system_error = boost::system::system_error; /// The type of error category used by the library using error_category = boost::system::error_category; +/// A function to return the generic error category used by the library +#if BEAST_DOXYGEN +error_category const& +generic_category(); +#else +using boost::system::generic_category; +#endif + /// A function to return the system error category used by the library -#if GENERATING_DOCS +#if BEAST_DOXYGEN error_category const& system_category(); #else @@ -35,7 +43,7 @@ using boost::system::system_category; using error_condition = boost::system::error_condition; /// The set of constants used for cross-platform error codes -#if GENERATING_DOCS +#if BEAST_DOXYGEN enum errc{}; #else namespace errc = boost::system::errc; diff --git a/include/beast/core/file.hpp b/include/beast/core/file.hpp new file mode 100644 index 0000000000..8b3eecc874 --- /dev/null +++ b/include/beast/core/file.hpp @@ -0,0 +1,41 @@ +// +// Copyright (c) 2015-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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_CORE_FILE_HPP +#define BEAST_CORE_FILE_HPP + +#include +#include +#include +#include +#include +#include + +namespace beast { + +/** An implementation of File. + + This alias is set to the best available implementation + of @b File given the platform and build settings. +*/ +#if BEAST_DOXYGEN +struct file : file_stdio +{ +}; +#else +#if BEAST_USE_WIN32_FILE +using file = file_win32; +#elif BEAST_USE_POSIX_FILE +using file = file_posix; +#else +using file = file_stdio; +#endif +#endif + +} // beast + +#endif diff --git a/include/beast/core/file_base.hpp b/include/beast/core/file_base.hpp new file mode 100644 index 0000000000..d39ebb520f --- /dev/null +++ b/include/beast/core/file_base.hpp @@ -0,0 +1,88 @@ +// +// Copyright (c) 2015-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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_CORE_FILE_BASE_HPP +#define BEAST_CORE_FILE_BASE_HPP + +#include +#include + +namespace beast { + +/// The type of file path used by the library +using file_path = string_view; + +/** File open modes + + These modes are used when opening files using + instances of the @b File concept. + + @see file_stdio +*/ +enum class file_mode +{ + /// Random reading + read, + + /// Sequential reading + scan, + + /** Random writing to a new or truncated file + + @li If the file does not exist, it is created. + + @li If the file exists, it is truncated to + zero size upon opening. + */ + write, + + /** Random writing to new file only + + If the file exists, an error is generated. + */ + write_new, + + /** Random writing to existing file + + If the file does not exist, an error is generated. + */ + write_existing, + + /** Appending to a new or truncated file + + The current file position shall be set to the end of + the file prior to each write. + + @li If the file does not exist, it is created. + + @li If the file exists, it is truncated to + zero size upon opening. + */ + append, + + /** Appending to a new file only + + The current file position shall be set to the end of + the file prior to each write. + + If the file exists, an error is generated. + */ + append_new, + + /** Appending to an existing file + + The current file position shall be set to the end of + the file prior to each write. + + If the file does not exist, an error is generated. + */ + append_existing +}; + +} // beast + +#endif diff --git a/include/beast/core/file_posix.hpp b/include/beast/core/file_posix.hpp new file mode 100644 index 0000000000..7e675f2402 --- /dev/null +++ b/include/beast/core/file_posix.hpp @@ -0,0 +1,171 @@ +// +// Copyright (c) 2015-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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_CORE_FILE_POSIX_HPP +#define BEAST_CORE_FILE_POSIX_HPP + +#include + +#if ! defined(BEAST_NO_POSIX_FILE) +# if ! defined(__APPLE__) && ! defined(__linux__) +# define BEAST_NO_POSIX_FILE +# endif +#endif + +#if ! defined(BEAST_USE_POSIX_FILE) +# if ! defined(BEAST_NO_POSIX_FILE) +# define BEAST_USE_POSIX_FILE 1 +# else +# define BEAST_USE_POSIX_FILE 0 +# endif +#endif + +#if BEAST_USE_POSIX_FILE + +#include +#include +#include + +namespace beast { + +/** An implementation of File for POSIX systems. + + This class implements a @b File using POSIX interfaces. +*/ +class file_posix +{ + int fd_ = -1; + +public: + /** The type of the underlying file handle. + + This is platform-specific. + */ + using native_handle_type = int; + + /** Destructor + + If the file is open it is first closed. + */ + ~file_posix(); + + /** Constructor + + There is no open file initially. + */ + file_posix() = default; + + /** Constructor + + The moved-from object behaves as if default constructed. + */ + file_posix(file_posix&& other); + + /** Assignment + + The moved-from object behaves as if default constructed. + */ + file_posix& operator=(file_posix&& other); + + /// Returns the native handle associated with the file. + native_handle_type + native_handle() const + { + return fd_; + } + + /** Set the native handle associated with the file. + + If the file is open it is first closed. + + @param fd The native file handle to assign. + */ + void + native_handle(native_handle_type fd); + + /// Returns `true` if the file is open + bool + is_open() const + { + return fd_ != -1; + } + + /** Close the file if open + + @param ec Set to the error, if any occurred. + */ + void + close(error_code& ec); + + /** Open a file at the given path with the specified mode + + @param path The utf-8 encoded path to the file + + @param mode The file mode to use + + @param ec Set to the error, if any occurred + */ + void + open(char const* path, file_mode mode, error_code& ec); + + /** Return the size of the open file + + @param ec Set to the error, if any occurred + + @return The size in bytes + */ + std::uint64_t + size(error_code& ec) const; + + /** Return the current position in the open file + + @param ec Set to the error, if any occurred + + @return The offset in bytes from the beginning of the file + */ + std::uint64_t + pos(error_code& ec) const; + + /** Adjust the current position in the open file + + @param offset The offset in bytes from the beginning of the file + + @param ec Set to the error, if any occurred + */ + void + seek(std::uint64_t offset, error_code& ec); + + /** Read from the open file + + @param buffer The buffer for storing the result of the read + + @param n The number of bytes to read + + @param ec Set to the error, if any occurred + */ + std::size_t + read(void* buffer, std::size_t n, error_code& ec) const; + + /** Write to the open file + + @param buffer The buffer holding the data to write + + @param n The number of bytes to write + + @param ec Set to the error, if any occurred + */ + std::size_t + write(void const* buffer, std::size_t n, error_code& ec); +}; + +} // beast + +#include + +#endif + +#endif diff --git a/include/beast/core/file_stdio.hpp b/include/beast/core/file_stdio.hpp new file mode 100644 index 0000000000..5b8753c609 --- /dev/null +++ b/include/beast/core/file_stdio.hpp @@ -0,0 +1,154 @@ +// +// Copyright (c) 2015-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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_CORE_FILE_STDIO_HPP +#define BEAST_CORE_FILE_STDIO_HPP + +#include +#include +#include +#include +#include + +namespace beast { + +/** An implementation of File which uses cstdio. + + This class implements a file using the interfaces present + in the C++ Standard Library, in ``. +*/ +class file_stdio +{ + FILE* f_ = nullptr; + +public: + /** The type of the underlying file handle. + + This is platform-specific. + */ + using native_handle_type = FILE*; + + /** Destructor + + If the file is open it is first closed. + */ + ~file_stdio(); + + /** Constructor + + There is no open file initially. + */ + file_stdio() = default; + + /** Constructor + + The moved-from object behaves as if default constructed. + */ + file_stdio(file_stdio&& other); + + /** Assignment + + The moved-from object behaves as if default constructed. + */ + file_stdio& operator=(file_stdio&& other); + + /// Returns the native handle associated with the file. + FILE* + native_handle() const + { + return f_; + } + + /** Set the native handle associated with the file. + + If the file is open it is first closed. + + @param f The native file handle to assign. + */ + void + native_handle(FILE* f); + + /// Returns `true` if the file is open + bool + is_open() const + { + return f_ != nullptr; + } + + /** Close the file if open + + @param ec Set to the error, if any occurred. + */ + void + close(error_code& ec); + + /** Open a file at the given path with the specified mode + + @param path The utf-8 encoded path to the file + + @param mode The file mode to use + + @param ec Set to the error, if any occurred + */ + void + open(char const* path, file_mode mode, error_code& ec); + + /** Return the size of the open file + + @param ec Set to the error, if any occurred + + @return The size in bytes + */ + std::uint64_t + size(error_code& ec) const; + + /** Return the current position in the open file + + @param ec Set to the error, if any occurred + + @return The offset in bytes from the beginning of the file + */ + std::uint64_t + pos(error_code& ec) const; + + /** Adjust the current position in the open file + + @param offset The offset in bytes from the beginning of the file + + @param ec Set to the error, if any occurred + */ + void + seek(std::uint64_t offset, error_code& ec); + + /** Read from the open file + + @param buffer The buffer for storing the result of the read + + @param n The number of bytes to read + + @param ec Set to the error, if any occurred + */ + std::size_t + read(void* buffer, std::size_t n, error_code& ec) const; + + /** Write to the open file + + @param buffer The buffer holding the data to write + + @param n The number of bytes to write + + @param ec Set to the error, if any occurred + */ + std::size_t + write(void const* buffer, std::size_t n, error_code& ec); +}; + +} // beast + +#include + +#endif diff --git a/include/beast/core/file_win32.hpp b/include/beast/core/file_win32.hpp new file mode 100644 index 0000000000..8aafd3450f --- /dev/null +++ b/include/beast/core/file_win32.hpp @@ -0,0 +1,173 @@ +// +// Copyright (c) 2015-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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_CORE_FILE_WIN32_HPP +#define BEAST_CORE_FILE_WIN32_HPP + +#include + +#if ! defined(BEAST_USE_WIN32_FILE) +# ifdef BOOST_MSVC +# define BEAST_USE_WIN32_FILE 1 +# else +# define BEAST_USE_WIN32_FILE 0 +# endif +#endif + +#if BEAST_USE_WIN32_FILE + +#include +#include +#include +#include +#include +#include + +namespace beast { + +/** An implementation of File for Win32. + + This class implements a @b File using Win32 native interfaces. +*/ +class file_win32 +{ + boost::detail::winapi::HANDLE_ h_ = + boost::detail::winapi::INVALID_HANDLE_VALUE_; + +public: + /** The type of the underlying file handle. + + This is platform-specific. + */ +#if BEAST_DOXYGEN + using native_handle_type = HANDLE; +#else + using native_handle_type = boost::detail::winapi::HANDLE_; +#endif + + /** Destructor + + If the file is open it is first closed. + */ + ~file_win32(); + + /** Constructor + + There is no open file initially. + */ + file_win32() = default; + + /** Constructor + + The moved-from object behaves as if default constructed. + */ + file_win32(file_win32&& other); + + /** Assignment + + The moved-from object behaves as if default constructed. + */ + file_win32& operator=(file_win32&& other); + + /// Returns the native handle associated with the file. + native_handle_type + native_handle() + { + return h_; + } + + /** Set the native handle associated with the file. + + If the file is open it is first closed. + + @param h The native file handle to assign. + */ + void + native_handle(native_handle_type h); + + /// Returns `true` if the file is open + bool + is_open() const + { + return h_ != boost::detail::winapi::INVALID_HANDLE_VALUE_; + } + + /** Close the file if open + + @param ec Set to the error, if any occurred. + */ + void + close(error_code& ec); + + /** Open a file at the given path with the specified mode + + @param path The utf-8 encoded path to the file + + @param mode The file mode to use + + @param ec Set to the error, if any occurred + */ + void + open(char const* path, file_mode mode, error_code& ec); + + /** Return the size of the open file + + @param ec Set to the error, if any occurred + + @return The size in bytes + */ + std::uint64_t + size(error_code& ec) const; + + /** Return the current position in the open file + + @param ec Set to the error, if any occurred + + @return The offset in bytes from the beginning of the file + */ + std::uint64_t + pos(error_code& ec); + + /** Adjust the current position in the open file + + @param offset The offset in bytes from the beginning of the file + + @param ec Set to the error, if any occurred + */ + void + seek(std::uint64_t offset, error_code& ec); + + /** Read from the open file + + @param buffer The buffer for storing the result of the read + + @param n The number of bytes to read + + @param ec Set to the error, if any occurred + */ + std::size_t + read(void* buffer, std::size_t n, error_code& ec); + + /** Write to the open file + + @param buffer The buffer holding the data to write + + @param n The number of bytes to write + + @param ec Set to the error, if any occurred + */ + std::size_t + write(void const* buffer, std::size_t n, error_code& ec); +}; + +} // beast + +#include + +#endif + +#endif diff --git a/include/beast/core/flat_buffer.hpp b/include/beast/core/flat_buffer.hpp new file mode 100644 index 0000000000..3d19b71db3 --- /dev/null +++ b/include/beast/core/flat_buffer.hpp @@ -0,0 +1,341 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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_FLAT_BUFFER_HPP +#define BEAST_FLAT_BUFFER_HPP + +#include +#include +#include +#include +#include + +namespace beast { + +/** A linear dynamic buffer. + + Objects of this type meet the requirements of @b DynamicBuffer + and offer additional invariants: + + @li Buffer sequences returned by @ref data and @ref prepare + will always be of length one. + + @li A configurable maximum buffer size may be set upon + construction. Attempts to exceed the buffer size will throw + `std::length_error`. + + Upon construction, a maximum size for the buffer may be + specified. If this limit is exceeded, the `std::length_error` + exception will be thrown. + + @note This class is designed for use with algorithms that + take dynamic buffers as parameters, and are optimized + for the case where the input sequence or output sequence + is stored in a single contiguous buffer. +*/ +template +class basic_flat_buffer +#if ! BEAST_DOXYGEN + : private detail::empty_base_optimization< + typename std::allocator_traits:: + template rebind_alloc> +#endif +{ +public: +#if BEAST_DOXYGEN + /// The type of allocator used. + using allocator_type = Allocator; +#else + using allocator_type = typename + std::allocator_traits:: + template rebind_alloc; +#endif + +private: + enum + { + min_size = 512 + }; + + template + friend class basic_flat_buffer; + + using alloc_traits = + std::allocator_traits; + + static + inline + std::size_t + dist(char const* first, char const* last) + { + return static_cast(last - first); + } + + char* begin_; + char* in_; + char* out_; + char* last_; + char* end_; + std::size_t max_; + +public: + /// The type used to represent the input sequence as a list of buffers. + using const_buffers_type = boost::asio::const_buffers_1; + + /// The type used to represent the output sequence as a list of buffers. + using mutable_buffers_type = boost::asio::mutable_buffers_1; + + /// Destructor + ~basic_flat_buffer(); + + /** Constructor + + Upon construction, capacity will be zero. + */ + basic_flat_buffer(); + + /** Constructor + + Upon construction, capacity will be zero. + + @param limit The setting for @ref max_size. + */ + explicit + basic_flat_buffer(std::size_t limit); + + /** Constructor + + Upon construction, capacity will be zero. + + @param alloc The allocator to construct with. + */ + explicit + basic_flat_buffer(Allocator const& alloc); + + /** Constructor + + Upon construction, capacity will be zero. + + @param limit The setting for @ref max_size. + + @param alloc The allocator to use. + */ + basic_flat_buffer( + std::size_t limit, Allocator const& alloc); + + /** Move constructor + + After the move, `*this` will have an empty output sequence. + + @param other The object to move from. After the move, + The object's state will be as if constructed using + its current allocator and limit. + */ + basic_flat_buffer(basic_flat_buffer&& other); + + /** Move constructor + + After the move, `*this` will have an empty output sequence. + + @param other The object to move from. After the move, + The object's state will be as if constructed using + its current allocator and limit. + + @param alloc The allocator to use. + */ + basic_flat_buffer( + basic_flat_buffer&& other, Allocator const& alloc); + + /** Copy constructor + + @param other The object to copy from. + */ + basic_flat_buffer(basic_flat_buffer const& other); + + /** Copy constructor + + @param other The object to copy from. + + @param alloc The allocator to use. + */ + basic_flat_buffer(basic_flat_buffer const& other, + Allocator const& alloc); + + /** Copy constructor + + @param other The object to copy from. + */ + template + basic_flat_buffer( + basic_flat_buffer const& other); + + /** Copy constructor + + @param other The object to copy from. + + @param alloc The allocator to use. + */ + template + basic_flat_buffer( + basic_flat_buffer const& other, + Allocator const& alloc); + + /** Move assignment + + After the move, `*this` will have an empty output sequence. + + @param other The object to move from. After the move, + The object's state will be as if constructed using + its current allocator and limit. + */ + basic_flat_buffer& + operator=(basic_flat_buffer&& other); + + /** Copy assignment + + After the copy, `*this` will have an empty output sequence. + + @param other The object to copy from. + */ + basic_flat_buffer& + operator=(basic_flat_buffer const& other); + + /** Copy assignment + + After the copy, `*this` will have an empty output sequence. + + @param other The object to copy from. + */ + template + basic_flat_buffer& + operator=(basic_flat_buffer const& other); + + /// Returns a copy of the associated allocator. + allocator_type + get_allocator() const + { + return this->member(); + } + + /// Returns the size of the input sequence. + std::size_t + size() const + { + return dist(in_, out_); + } + + /// Return the maximum sum of the input and output sequence sizes. + std::size_t + max_size() const + { + return max_; + } + + /// Return the maximum sum of input and output sizes that can be held without an allocation. + std::size_t + capacity() const + { + return dist(begin_, end_); + } + + /// Get a list of buffers that represent the input sequence. + const_buffers_type + data() const + { + return {in_, dist(in_, out_)}; + } + + /** Get a list of buffers that represent the output sequence, with the given size. + + @throws std::length_error if `size() + n` exceeds `max_size()`. + + @note All previous buffers sequences obtained from + calls to @ref data or @ref prepare are invalidated. + */ + mutable_buffers_type + prepare(std::size_t n); + + /** Move bytes from the output sequence to the input sequence. + + @param n The number of bytes to move. If this is larger than + the number of bytes in the output sequences, then the entire + output sequences is moved. + + @note All previous buffers sequences obtained from + calls to @ref data or @ref prepare are invalidated. + */ + void + commit(std::size_t n) + { + out_ += (std::min)(n, dist(out_, last_)); + } + + /** Remove bytes from the input sequence. + + If `n` is greater than the number of bytes in the input + sequence, all bytes in the input sequence are removed. + + @note All previous buffers sequences obtained from + calls to @ref data or @ref prepare are invalidated. + */ + void + consume(std::size_t n); + + /** Reallocate the buffer to fit the input sequence. + + @note All previous buffers sequences obtained from + calls to @ref data or @ref prepare are invalidated. + */ + void + shrink_to_fit(); + + /// Exchange two flat buffers + template + friend + void + swap( + basic_flat_buffer& lhs, + basic_flat_buffer& rhs); + +private: + void + reset(); + + template + void + copy_from(DynamicBuffer const& other); + + void + move_assign(basic_flat_buffer&, std::true_type); + + void + move_assign(basic_flat_buffer&, std::false_type); + + void + copy_assign(basic_flat_buffer const&, std::true_type); + + void + copy_assign(basic_flat_buffer const&, std::false_type); + + void + swap(basic_flat_buffer&); + + void + swap(basic_flat_buffer&, std::true_type); + + void + swap(basic_flat_buffer&, std::false_type); +}; + +using flat_buffer = + basic_flat_buffer>; + +} // beast + +#include + +#endif diff --git a/include/beast/core/handler_alloc.hpp b/include/beast/core/handler_alloc.hpp index 08a395c3ec..b7656a8835 100644 --- a/include/beast/core/handler_alloc.hpp +++ b/include/beast/core/handler_alloc.hpp @@ -9,7 +9,9 @@ #define BEAST_HANDLER_ALLOC_HPP #include -#include +#include +#include +#include #include #include #include @@ -35,7 +37,7 @@ namespace beast { the handler is invoked or undefined behavior results. This behavior is described as the "deallocate before invocation" Asio guarantee. */ -#if GENERATING_DOCS +#if BEAST_DOXYGEN template class handler_alloc; #else @@ -56,6 +58,12 @@ private: public: using value_type = T; using is_always_equal = std::true_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 struct rebind @@ -89,38 +97,46 @@ public: } value_type* - allocate(std::ptrdiff_t n) + allocate(size_type n) { auto const size = n * sizeof(T); + using boost::asio::asio_handler_allocate; return static_cast( - beast_asio_helpers::allocate( - size, h_)); + asio_handler_allocate(size, std::addressof(h_))); } void - deallocate(value_type* p, std::ptrdiff_t n) + deallocate(value_type* p, size_type n) { auto const size = n * sizeof(T); - beast_asio_helpers::deallocate( - p, size, h_); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate(p, size, std::addressof(h_)); } -#ifdef _MSC_VER - // Work-around for MSVC not using allocator_traits - // in the implementation of shared_ptr - // +//#if BOOST_WORKAROUND(BOOST_GCC, < 60000) // Works, but too coarse + +#if defined(BOOST_LIBSTDCXX_VERSION) && BOOST_LIBSTDCXX_VERSION < 60000 + template void - destroy(T* t) + construct(U* ptr, Args&&... args) { - t->~T(); + ::new((void*)ptr) U(std::forward(args)...); + } + + template + void + destroy(U* ptr) + { + ptr->~U(); } #endif template friend bool - operator==(handler_alloc const& lhs, - handler_alloc const& rhs) + operator==( + handler_alloc const&, + handler_alloc const&) { return true; } @@ -128,7 +144,8 @@ public: template friend bool - operator!=(handler_alloc const& lhs, + operator!=( + handler_alloc const& lhs, handler_alloc const& rhs) { return ! (lhs == rhs); diff --git a/include/beast/core/handler_concepts.hpp b/include/beast/core/handler_concepts.hpp deleted file mode 100644 index e118072aeb..0000000000 --- a/include/beast/core/handler_concepts.hpp +++ /dev/null @@ -1,28 +0,0 @@ -// -// 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_HANDLER_CONCEPTS_HPP -#define BEAST_HANDLER_CONCEPTS_HPP - -#include -#include -#include - -namespace beast { - -/// Determine if `T` meets the requirements of @b `CompletionHandler`. -template -#if GENERATING_DOCS -using is_CompletionHandler = std::integral_constant; -#else -using is_CompletionHandler = std::integral_constant::type>::value && - detail::is_call_possible::value>; -#endif -} // beast - -#endif diff --git a/include/beast/core/handler_helpers.hpp b/include/beast/core/handler_helpers.hpp deleted file mode 100644 index 53112e7f15..0000000000 --- a/include/beast/core/handler_helpers.hpp +++ /dev/null @@ -1,105 +0,0 @@ -// -// 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_HANDLER_HELPERS_HPP -#define BEAST_HANDLER_HELPERS_HPP - -#include -#include -#include -#include -#include - -/* Calls to: - - * asio_handler_allocate - * asio_handler_deallocate - * asio_handler_invoke - * asio_handler_is_continuation - - must be made from a namespace that does not - contain overloads of this function. The beast_asio_helpers - namespace is defined here for that purpose. -*/ - -namespace beast_asio_helpers { - -/// Allocation function for handlers. -template -inline -void* -allocate(std::size_t s, Handler& handler) -{ -#if !defined(BOOST_ASIO_HAS_HANDLER_HOOKS) - return ::operator new(s); -#else - using boost::asio::asio_handler_allocate; - return asio_handler_allocate(s, std::addressof(handler)); -#endif -} - -/// Deallocation function for handlers. -template -inline -void -deallocate(void* p, std::size_t s, Handler& handler) -{ -#if !defined(BOOST_ASIO_HAS_HANDLER_HOOKS) - ::operator delete(p); -#else - using boost::asio::asio_handler_deallocate; - asio_handler_deallocate(p, s, std::addressof(handler)); -#endif -} - -/// Invoke function for handlers. -template -inline -void -invoke(Function& function, Handler& handler) -{ -#if !defined(BOOST_ASIO_HAS_HANDLER_HOOKS) - Function tmp(function); - tmp(); -#else - using boost::asio::asio_handler_invoke; - asio_handler_invoke(function, std::addressof(handler)); -#endif -} - -/// Invoke function for handlers. -template -inline -void -invoke(Function const& function, Handler& handler) -{ -#if !defined(BOOST_ASIO_HAS_HANDLER_HOOKS) - Function tmp(function); - tmp(); -#else - using boost::asio::asio_handler_invoke; - asio_handler_invoke(function, std::addressof(handler)); -#endif -} - -/// Returns true if handler represents a continuation of the asynchronous operation -template -inline -bool -is_continuation(Handler& handler) -{ -#if !defined(BOOST_ASIO_HAS_HANDLER_HOOKS) - return false; -#else - using boost::asio::asio_handler_is_continuation; - return asio_handler_is_continuation(std::addressof(handler)); -#endif -} - -} // beast_asio_helpers - -#endif diff --git a/include/beast/core/handler_ptr.hpp b/include/beast/core/handler_ptr.hpp index 52650656e4..c288c4d1f9 100644 --- a/include/beast/core/handler_ptr.hpp +++ b/include/beast/core/handler_ptr.hpp @@ -191,6 +191,11 @@ public: deallocation-before-invocation Asio guarantee. All instances of @ref handler_ptr which refer to the same owned object will be reset, including this instance. + + @note Care must be taken when the arguments are themselves + stored in the owned object. Such arguments must first be + moved to the stack or elsewhere, and then passed, or else + undefined behavior will result. */ template void diff --git a/include/beast/core/detail/buffer_cat.hpp b/include/beast/core/impl/buffer_cat.ipp similarity index 73% rename from include/beast/core/detail/buffer_cat.hpp rename to include/beast/core/impl/buffer_cat.ipp index 60d840c220..f0f9d45eaf 100644 --- a/include/beast/core/detail/buffer_cat.hpp +++ b/include/beast/core/impl/buffer_cat.ipp @@ -5,12 +5,13 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_DETAIL_BUFFER_CAT_HPP -#define BEAST_DETAIL_BUFFER_CAT_HPP +#ifndef BEAST_IMPL_BUFFER_CAT_IPP +#define BEAST_IMPL_BUFFER_CAT_IPP -#include #include #include +#include +#include #include #include #include @@ -19,57 +20,22 @@ #include namespace beast { -namespace detail { template -struct common_buffers_type +class buffer_cat_view::const_iterator { - using type = typename std::conditional< - std::is_convertible, - typename repeat_tuple::type>::value, - boost::asio::mutable_buffer, - boost::asio::const_buffer>::type; -}; +#if 0 + static_assert( + detail::is_all_const_buffer_sequence::value, + "BufferSequence requirements not met"); +#endif -template -class buffer_cat_helper -{ - std::tuple bn_; - -public: - using value_type = typename - common_buffers_type::type; - - class const_iterator; - - buffer_cat_helper(buffer_cat_helper&&) = default; - buffer_cat_helper(buffer_cat_helper const&) = default; - buffer_cat_helper& operator=(buffer_cat_helper&&) = delete; - buffer_cat_helper& operator=(buffer_cat_helper const&) = delete; - - explicit - buffer_cat_helper(Bn const&... bn) - : bn_(bn...) - { - } - - const_iterator - begin() const; - - const_iterator - end() const; -}; - -template -class buffer_cat_helper::const_iterator -{ std::size_t n_; std::tuple const* bn_; - std::array()> buf_; + std::array()> buf_; - friend class buffer_cat_helper; + friend class buffer_cat_view; template using C = std::integral_constant; @@ -84,8 +50,7 @@ class buffer_cat_helper::const_iterator { // type-pun return *reinterpret_cast< - iter_t*>(static_cast( - buf_.data())); + iter_t*>(static_cast(buf_.data())); } template @@ -100,7 +65,7 @@ class buffer_cat_helper::const_iterator public: using value_type = typename - common_buffers_type::type; + detail::common_buffers_type::type; using pointer = value_type const*; using reference = value_type; using difference_type = std::ptrdiff_t; @@ -120,7 +85,7 @@ public: bool operator!=(const_iterator const& other) const { - return !(*this == other); + return ! (*this == other); } reference @@ -133,23 +98,13 @@ public: operator++(); const_iterator - operator++(int) - { - auto temp = *this; - ++(*this); - return temp; - } + operator++(int); const_iterator& operator--(); const_iterator - operator--(int) - { - auto temp = *this; - --(*this); - return temp; - } + operator--(int); private: const_iterator( @@ -166,17 +121,48 @@ private: void construct(C const&) { - if(std::get(*bn_).begin() != - std::get(*bn_).end()) + if(boost::asio::buffer_size( + std::get(*bn_)) != 0) { n_ = I; - new(buf_.data()) iter_t{ + new(&buf_[0]) iter_t{ std::get(*bn_).begin()}; return; } construct(C{}); } + void + rconstruct(C<0> const&) + { + auto constexpr I = 0; + if(boost::asio::buffer_size( + std::get(*bn_)) != 0) + { + n_ = I; + new(&buf_[0]) iter_t{ + std::get(*bn_).end()}; + return; + } + BOOST_THROW_EXCEPTION(std::logic_error{ + "invalid iterator"}); + } + + template + void + rconstruct(C const&) + { + if(boost::asio::buffer_size( + std::get(*bn_)) != 0) + { + n_ = I; + new(&buf_[0]) iter_t{ + std::get(*bn_).end()}; + return; + } + rconstruct(C{}); + } + void destroy(C const&) { @@ -209,7 +195,7 @@ private: { if(n_ == I) { - new(buf_.data()) iter_t{ + new(&buf_[0]) iter_t{ std::move(other.iter())}; return; } @@ -229,7 +215,7 @@ private: { if(n_ == I) { - new(buf_.data()) iter_t{ + new(&buf_[0]) iter_t{ other.iter()}; return; } @@ -257,8 +243,8 @@ private: reference dereference(C const&) const { - throw detail::make_exception( - "invalid iterator", __FILE__, __LINE__); + BOOST_THROW_EXCEPTION(std::logic_error{ + "invalid iterator"}); } template @@ -274,8 +260,8 @@ private: void increment(C const&) { - throw detail::make_exception( - "invalid iterator", __FILE__, __LINE__); + BOOST_THROW_EXCEPTION(std::logic_error{ + "invalid iterator"}); } template @@ -299,27 +285,10 @@ private: { auto constexpr I = sizeof...(Bn); if(n_ == I) - { - --n_; - new(buf_.data()) iter_t{ - std::get(*bn_).end()}; - } + rconstruct(C{}); decrement(C{}); } - void - decrement(C<0> const&) - { - auto constexpr I = 0; - if(iter() != std::get(*bn_).begin()) - { - --iter(); - return; - } - throw detail::make_exception( - "invalid iterator", __FILE__, __LINE__); - } - template void decrement(C const&) @@ -334,24 +303,36 @@ private: --n_; using Iter = iter_t; iter().~Iter(); - new(buf_.data()) iter_t{ - std::get(*bn_).end()}; + rconstruct(C{}); } decrement(C{}); } + + void + decrement(C<0> const&) + { + auto constexpr I = 0; + if(iter() != std::get(*bn_).begin()) + { + --iter(); + return; + } + BOOST_THROW_EXCEPTION(std::logic_error{ + "invalid iterator"}); + } }; //------------------------------------------------------------------------------ template -buffer_cat_helper:: +buffer_cat_view:: const_iterator::~const_iterator() { destroy(C<0>{}); } template -buffer_cat_helper:: +buffer_cat_view:: const_iterator::const_iterator() : n_(sizeof...(Bn)) , bn_(nullptr) @@ -359,7 +340,7 @@ const_iterator::const_iterator() } template -buffer_cat_helper:: +buffer_cat_view:: const_iterator::const_iterator( std::tuple const& bn, bool at_end) : bn_(&bn) @@ -371,7 +352,7 @@ const_iterator::const_iterator( } template -buffer_cat_helper:: +buffer_cat_view:: const_iterator::const_iterator(const_iterator&& other) : n_(other.n_) , bn_(other.bn_) @@ -380,7 +361,7 @@ const_iterator::const_iterator(const_iterator&& other) } template -buffer_cat_helper:: +buffer_cat_view:: const_iterator::const_iterator(const_iterator const& other) : n_(other.n_) , bn_(other.bn_) @@ -390,7 +371,7 @@ const_iterator::const_iterator(const_iterator const& other) template auto -buffer_cat_helper:: +buffer_cat_view:: const_iterator::operator=(const_iterator&& other) -> const_iterator& { @@ -399,13 +380,14 @@ const_iterator::operator=(const_iterator&& other) -> destroy(C<0>{}); n_ = other.n_; bn_ = other.bn_; + // VFALCO What about exceptions? move(std::move(other), C<0>{}); return *this; } template auto -buffer_cat_helper:: +buffer_cat_view:: const_iterator::operator=(const_iterator const& other) -> const_iterator& { @@ -414,13 +396,14 @@ const_iterator& destroy(C<0>{}); n_ = other.n_; bn_ = other.bn_; + // VFALCO What about exceptions? copy(other, C<0>{}); return *this; } template bool -buffer_cat_helper:: +buffer_cat_view:: const_iterator::operator==(const_iterator const& other) const { if(bn_ != other.bn_) @@ -432,7 +415,7 @@ const_iterator::operator==(const_iterator const& other) const template auto -buffer_cat_helper:: +buffer_cat_view:: const_iterator::operator*() const -> reference { @@ -441,7 +424,7 @@ const_iterator::operator*() const -> template auto -buffer_cat_helper:: +buffer_cat_view:: const_iterator::operator++() -> const_iterator& { @@ -451,7 +434,18 @@ const_iterator::operator++() -> template auto -buffer_cat_helper:: +buffer_cat_view:: +const_iterator::operator++(int) -> + const_iterator +{ + auto temp = *this; + ++(*this); + return temp; +} + +template +auto +buffer_cat_view:: const_iterator::operator--() -> const_iterator& { @@ -459,10 +453,31 @@ const_iterator::operator--() -> return *this; } +template +auto +buffer_cat_view:: +const_iterator::operator--(int) -> + const_iterator +{ + auto temp = *this; + --(*this); + return temp; +} + +//------------------------------------------------------------------------------ + +template +buffer_cat_view:: +buffer_cat_view(Bn const&... bn) + : bn_(bn...) +{ +} + + template inline auto -buffer_cat_helper::begin() const -> +buffer_cat_view::begin() const -> const_iterator { return const_iterator{bn_, false}; @@ -471,13 +486,12 @@ buffer_cat_helper::begin() const -> template inline auto -buffer_cat_helper::end() const -> +buffer_cat_view::end() const -> const_iterator { return const_iterator{bn_, true}; } -} // detail } // beast #endif diff --git a/include/beast/core/detail/prepare_buffers.hpp b/include/beast/core/impl/buffer_prefix.ipp similarity index 54% rename from include/beast/core/detail/prepare_buffers.hpp rename to include/beast/core/impl/buffer_prefix.ipp index 4f9441c9b3..b3aa55782a 100644 --- a/include/beast/core/detail/prepare_buffers.hpp +++ b/include/beast/core/impl/buffer_prefix.ipp @@ -5,11 +5,9 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_DETAIL_PREPARED_BUFFERS_HPP -#define BEAST_DETAIL_PREPARED_BUFFERS_HPP +#ifndef BEAST_IMPL_BUFFER_PREFIX_IPP +#define BEAST_IMPL_BUFFER_PREFIX_IPP -#include -#include #include #include #include @@ -18,102 +16,40 @@ #include namespace beast { + namespace detail { -/** A buffer sequence adapter that shortens the sequence size. - - The class adapts a buffer sequence to efficiently represent - a shorter subset of the original list of buffers starting - with the first byte of the original sequence. - - @tparam BufferSequence The buffer sequence to adapt. -*/ -template -class prepared_buffers +inline +boost::asio::const_buffer +buffer_prefix(std::size_t n, + boost::asio::const_buffer buffer) { - using iter_type = - typename BufferSequence::const_iterator; + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + return { buffer_cast(buffer), + (std::min)(n, buffer_size(buffer)) }; +} - BufferSequence bs_; - iter_type back_; - iter_type end_; - std::size_t size_; +inline +boost::asio::mutable_buffer +buffer_prefix(std::size_t n, + boost::asio::mutable_buffer buffer) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + return { buffer_cast(buffer), + (std::min)(n, buffer_size(buffer)) }; +} - template - prepared_buffers(Deduced&& other, - std::size_t nback, std::size_t nend) - : bs_(std::forward(other).bs_) - , back_(std::next(bs_.begin(), nback)) - , end_(std::next(bs_.begin(), nend)) - , size_(other.size_) - { - } - - void - setup(std::size_t n); - -public: - /// The type for each element in the list of buffers. - using value_type = typename std::conditional< - std::is_convertible::value_type, - boost::asio::mutable_buffer>::value, - boost::asio::mutable_buffer, - boost::asio::const_buffer>::type; - -#if GENERATING_DOCS - /// A bidirectional iterator type that may be used to read elements. - using const_iterator = implementation_defined; - -#else - class const_iterator; - -#endif - - /// Move constructor. - prepared_buffers(prepared_buffers&&); - - /// Copy constructor. - prepared_buffers(prepared_buffers const&); - - /// Move assignment. - prepared_buffers& operator=(prepared_buffers&&); - - /// Copy assignment. - prepared_buffers& operator=(prepared_buffers const&); - - /** Construct a shortened buffer sequence. - - @param n The maximum number of bytes in the wrapped - sequence. If this is larger than the size of passed, - buffers, the resulting sequence will represent the - entire input sequence. - - @param buffers The buffer sequence to adapt. A copy of - the sequence will be made, but ownership of the underlying - memory is not transferred. - */ - prepared_buffers(std::size_t n, BufferSequence const& buffers); - - /// Get a bidirectional iterator to the first element. - const_iterator - begin() const; - - /// Get a bidirectional iterator to one past the last element. - const_iterator - end() const; -}; +} // detail template -class prepared_buffers::const_iterator +class buffer_prefix_view::const_iterator { - friend class prepared_buffers; + friend class buffer_prefix_view; - using iter_type = - typename BufferSequence::const_iterator; - - prepared_buffers const* b_ = nullptr; - typename BufferSequence::const_iterator it_; + buffer_prefix_view const* b_ = nullptr; + iter_type it_; public: using value_type = typename std::conditional< @@ -150,7 +86,7 @@ public: operator*() const { if(it_ == b_->back_) - return prepare_buffer(b_->size_, *it_); + return detail::buffer_prefix(b_->size_, *it_); return *it_; } @@ -188,7 +124,7 @@ public: } private: - const_iterator(prepared_buffers const& b, + const_iterator(buffer_prefix_view const& b, bool at_end) : b_(&b) , it_(at_end ? b.end_ : b.bs_.begin()) @@ -198,7 +134,7 @@ private: template void -prepared_buffers:: +buffer_prefix_view:: setup(std::size_t n) { for(end_ = bs_.begin(); end_ != bs_.end(); ++end_) @@ -218,7 +154,8 @@ setup(std::size_t n) } template -prepared_buffers::const_iterator:: +buffer_prefix_view:: +const_iterator:: const_iterator(const_iterator&& other) : b_(other.b_) , it_(std::move(other.it_)) @@ -226,7 +163,8 @@ const_iterator(const_iterator&& other) } template -prepared_buffers::const_iterator:: +buffer_prefix_view:: +const_iterator:: const_iterator(const_iterator const& other) : b_(other.b_) , it_(other.it_) @@ -235,7 +173,8 @@ const_iterator(const_iterator const& other) template auto -prepared_buffers::const_iterator:: +buffer_prefix_view:: +const_iterator:: operator=(const_iterator&& other) -> const_iterator& { @@ -246,7 +185,8 @@ operator=(const_iterator&& other) -> template auto -prepared_buffers::const_iterator:: +buffer_prefix_view:: +const_iterator:: operator=(const_iterator const& other) -> const_iterator& { @@ -258,18 +198,18 @@ operator=(const_iterator const& other) -> } template -prepared_buffers:: -prepared_buffers(prepared_buffers&& other) - : prepared_buffers(std::move(other), +buffer_prefix_view:: +buffer_prefix_view(buffer_prefix_view&& other) + : buffer_prefix_view(std::move(other), std::distance(other.bs_.begin(), other.back_), std::distance(other.bs_.begin(), other.end_)) { } template -prepared_buffers:: -prepared_buffers(prepared_buffers const& other) - : prepared_buffers(other, +buffer_prefix_view:: +buffer_prefix_view(buffer_prefix_view const& other) + : buffer_prefix_view(other, std::distance(other.bs_.begin(), other.back_), std::distance(other.bs_.begin(), other.end_)) { @@ -277,9 +217,9 @@ prepared_buffers(prepared_buffers const& other) template auto -prepared_buffers:: -operator=(prepared_buffers&& other) -> - prepared_buffers& +buffer_prefix_view:: +operator=(buffer_prefix_view&& other) -> + buffer_prefix_view& { auto const nback = std::distance( other.bs_.begin(), other.back_); @@ -294,9 +234,9 @@ operator=(prepared_buffers&& other) -> template auto -prepared_buffers:: -operator=(prepared_buffers const& other) -> - prepared_buffers& +buffer_prefix_view:: +operator=(buffer_prefix_view const& other) -> + buffer_prefix_view& { auto const nback = std::distance( other.bs_.begin(), other.back_); @@ -310,17 +250,27 @@ operator=(prepared_buffers const& other) -> } template -prepared_buffers:: -prepared_buffers(std::size_t n, BufferSequence const& bs) +buffer_prefix_view:: +buffer_prefix_view(std::size_t n, BufferSequence const& bs) : bs_(bs) { setup(n); } +template +template +buffer_prefix_view:: +buffer_prefix_view(std::size_t n, + boost::in_place_init_t, Args&&... args) + : bs_(std::forward(args)...) +{ + setup(n); +} + template inline auto -prepared_buffers::begin() const -> +buffer_prefix_view::begin() const -> const_iterator { return const_iterator{*this, false}; @@ -329,13 +279,12 @@ prepared_buffers::begin() const -> template inline auto -prepared_buffers::end() const -> +buffer_prefix_view::end() const -> const_iterator { return const_iterator{*this, true}; } -} // detail } // beast #endif diff --git a/include/beast/core/impl/dynabuf_readstream.ipp b/include/beast/core/impl/buffered_read_stream.ipp similarity index 50% rename from include/beast/core/impl/dynabuf_readstream.ipp rename to include/beast/core/impl/buffered_read_stream.ipp index 5a0815baac..d78c136149 100644 --- a/include/beast/core/impl/dynabuf_readstream.ipp +++ b/include/beast/core/impl/buffered_read_stream.ipp @@ -5,38 +5,30 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_IMPL_DYNABUF_READSTREAM_HPP -#define BEAST_IMPL_DYNABUF_READSTREAM_HPP +#ifndef BEAST_IMPL_BUFFERED_READ_STREAM_IPP +#define BEAST_IMPL_BUFFERED_READ_STREAM_IPP #include #include -#include -#include #include +#include +#include +#include +#include +#include +#include namespace beast { template template -class dynabuf_readstream< +class buffered_read_stream< Stream, DynamicBuffer>::read_some_op { - // VFALCO What about bool cont for is_continuation? - struct data - { - dynabuf_readstream& srs; - MutableBufferSequence bs; - int state = 0; - - data(Handler&, dynabuf_readstream& srs_, - MutableBufferSequence const& bs_) - : srs(srs_) - , bs(bs_) - { - } - }; - - handler_ptr d_; + int step_ = 0; + buffered_read_stream& s_; + MutableBufferSequence b_; + Handler h_; public: read_some_op(read_some_op&&) = default; @@ -44,11 +36,12 @@ public: template read_some_op(DeducedHandler&& h, - dynabuf_readstream& srs, Args&&... args) - : d_(std::forward(h), - srs, std::forward(args)...) + buffered_read_stream& s, + MutableBufferSequence const& b) + : s_(s) + , b_(b) + , h_(std::forward(h)) { - (*this)(error_code{}, 0); } void @@ -59,99 +52,92 @@ public: void* asio_handler_allocate( std::size_t size, read_some_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->h_)); } friend void asio_handler_deallocate( void* p, std::size_t size, read_some_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->h_)); } friend bool asio_handler_is_continuation(read_some_op* op) { - return beast_asio_helpers:: - is_continuation(op->d_.handler()); + using boost::asio::asio_handler_is_continuation; + return asio_handler_is_continuation( + std::addressof(op->h_)); } template friend void asio_handler_invoke(Function&& f, read_some_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke(f, std::addressof(op->h_)); } }; template template void -dynabuf_readstream:: +buffered_read_stream:: read_some_op::operator()( error_code const& ec, std::size_t bytes_transferred) { - auto& d = *d_; - while(! ec && d.state != 99) + switch(step_) { - switch(d.state) + case 0: + if(s_.sb_.size() == 0) { - case 0: - if(d.srs.sb_.size() == 0) + if(s_.capacity_ == 0) { - d.state = - d.srs.capacity_ > 0 ? 2 : 1; - break; + // read (unbuffered) + step_ = 1; + return s_.next_layer_.async_read_some( + b_, std::move(*this)); } - d.state = 4; - d.srs.get_io_service().post( - bind_handler(std::move(*this), ec, 0)); - return; - case 1: - // read (unbuffered) - d.state = 99; - d.srs.next_layer_.async_read_some( - d.bs, std::move(*this)); - return; - - case 2: // read - d.state = 3; - d.srs.next_layer_.async_read_some( - d.srs.sb_.prepare(d.srs.capacity_), + step_ = 2; + return s_.next_layer_.async_read_some( + s_.sb_.prepare(s_.capacity_), std::move(*this)); - return; - // got data - case 3: - d.state = 4; - d.srs.sb_.commit(bytes_transferred); - break; - - // copy - case 4: - bytes_transferred = - boost::asio::buffer_copy( - d.bs, d.srs.sb_.data()); - d.srs.sb_.consume(bytes_transferred); - // call handler - d.state = 99; - break; } + step_ = 3; + s_.get_io_service().post( + bind_handler(std::move(*this), ec, 0)); + return; + + case 1: + // upcall + break; + + case 2: + s_.sb_.commit(bytes_transferred); + BEAST_FALLTHROUGH; + + case 3: + bytes_transferred = + boost::asio::buffer_copy(b_, s_.sb_.data()); + s_.sb_.consume(bytes_transferred); + break; } - d_.invoke(ec, bytes_transferred); + h_(ec, bytes_transferred); } //------------------------------------------------------------------------------ template template -dynabuf_readstream:: -dynabuf_readstream(Args&&... args) +buffered_read_stream:: +buffered_read_stream(Args&&... args) : next_layer_(std::forward(args)...) { } @@ -159,18 +145,17 @@ dynabuf_readstream(Args&&... args) template template auto -dynabuf_readstream:: +buffered_read_stream:: async_write_some(ConstBufferSequence const& buffers, - WriteHandler&& handler) -> - typename async_completion< - WriteHandler, void(error_code)>::result_type + WriteHandler&& handler) -> + async_return_type { - static_assert(is_AsyncWriteStream::value, + static_assert(is_async_write_stream::value, "AsyncWriteStream requirements not met"); - static_assert(is_ConstBufferSequence< + static_assert(is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); - static_assert(is_CompletionHandler::value, "WriteHandler requirements not met"); return next_layer_.async_write_some(buffers, @@ -180,32 +165,32 @@ async_write_some(ConstBufferSequence const& buffers, template template std::size_t -dynabuf_readstream:: +buffered_read_stream:: read_some( MutableBufferSequence const& buffers) { - static_assert(is_SyncReadStream::value, + static_assert(is_sync_read_stream::value, "SyncReadStream requirements not met"); - static_assert(is_MutableBufferSequence< + static_assert(is_mutable_buffer_sequence< MutableBufferSequence>::value, "MutableBufferSequence requirements not met"); error_code ec; auto n = read_some(buffers, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); return n; } template template std::size_t -dynabuf_readstream:: +buffered_read_stream:: read_some(MutableBufferSequence const& buffers, error_code& ec) { - static_assert(is_SyncReadStream::value, + static_assert(is_sync_read_stream::value, "SyncReadStream requirements not met"); - static_assert(is_MutableBufferSequence< + static_assert(is_mutable_buffer_sequence< MutableBufferSequence>::value, "MutableBufferSequence requirements not met"); using boost::asio::buffer_size; @@ -219,6 +204,10 @@ read_some(MutableBufferSequence const& buffers, if(ec) return 0; } + else + { + ec.assign(0, ec.category()); + } auto bytes_transferred = buffer_copy(buffers, sb_.data()); sb_.consume(bytes_transferred); @@ -228,25 +217,23 @@ read_some(MutableBufferSequence const& buffers, template template auto -dynabuf_readstream:: -async_read_some( - MutableBufferSequence const& buffers, +buffered_read_stream:: +async_read_some(MutableBufferSequence const& buffers, ReadHandler&& handler) -> - typename async_completion< - ReadHandler, void(error_code)>::result_type + async_return_type { - static_assert(is_AsyncReadStream::value, + static_assert(is_async_read_stream::value, "Stream requirements not met"); - static_assert(is_MutableBufferSequence< + static_assert(is_mutable_buffer_sequence< MutableBufferSequence>::value, "MutableBufferSequence requirements not met"); - beast::async_completion< - ReadHandler, void(error_code, std::size_t) - > completion{handler}; - read_some_op{ - completion.handler, *this, buffers}; - return completion.result.get(); + async_completion init{handler}; + read_some_op>{ + init.completion_handler, *this, buffers}( + error_code{}, 0); + return init.result.get(); } } // beast diff --git a/include/beast/core/impl/buffers_adapter.ipp b/include/beast/core/impl/buffers_adapter.ipp index 286cfa86ff..3216667f1b 100644 --- a/include/beast/core/impl/buffers_adapter.ipp +++ b/include/beast/core/impl/buffers_adapter.ipp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -414,8 +415,8 @@ buffers_adapter::prepare(std::size_t n) -> } } if(n > 0) - throw detail::make_exception( - "no space", __FILE__, __LINE__); + BOOST_THROW_EXCEPTION(std::length_error{ + "buffer overflow"}); return mutable_buffers_type{*this}; } diff --git a/include/beast/core/impl/consuming_buffers.ipp b/include/beast/core/impl/consuming_buffers.ipp index cebf1195c4..fca247ef13 100644 --- a/include/beast/core/impl/consuming_buffers.ipp +++ b/include/beast/core/impl/consuming_buffers.ipp @@ -8,8 +8,7 @@ #ifndef BEAST_IMPL_CONSUMING_BUFFERS_IPP #define BEAST_IMPL_CONSUMING_BUFFERS_IPP -#include -#include +#include #include #include #include @@ -18,13 +17,13 @@ namespace beast { -template -class consuming_buffers::const_iterator +template +class consuming_buffers::const_iterator { - friend class consuming_buffers; + friend class consuming_buffers; using iter_type = - typename BufferSequence::const_iterator; + typename Buffers::const_iterator; iter_type it_; consuming_buffers const* b_ = nullptr; @@ -110,8 +109,17 @@ private: } }; -template -consuming_buffers:: +//------------------------------------------------------------------------------ + +template +consuming_buffers:: +consuming_buffers() + : begin_(bs_.begin()) +{ +} + +template +consuming_buffers:: consuming_buffers(consuming_buffers&& other) : consuming_buffers(std::move(other), std::distance( @@ -119,8 +127,8 @@ consuming_buffers(consuming_buffers&& other) { } -template -consuming_buffers:: +template +consuming_buffers:: consuming_buffers(consuming_buffers const& other) : consuming_buffers(other, std::distance( @@ -128,9 +136,35 @@ consuming_buffers(consuming_buffers const& other) { } -template +template +consuming_buffers:: +consuming_buffers(Buffers const& bs) + : bs_(bs) + , begin_(bs_.begin()) +{ + static_assert( + is_const_buffer_sequence::value|| + is_mutable_buffer_sequence::value, + "BufferSequence requirements not met"); +} + +template +template +consuming_buffers:: +consuming_buffers(boost::in_place_init_t, Args&&... args) + : bs_(std::forward(args)...) + , begin_(bs_.begin()) +{ + static_assert(sizeof...(Args) > 0, + "Missing constructor arguments"); + static_assert( + std::is_constructible::value, + "Buffers not constructible from arguments"); +} + +template auto -consuming_buffers:: +consuming_buffers:: operator=(consuming_buffers&& other) -> consuming_buffers& { @@ -142,9 +176,9 @@ operator=(consuming_buffers&& other) -> return *this; } -template +template auto -consuming_buffers:: +consuming_buffers:: operator=(consuming_buffers const& other) -> consuming_buffers& { @@ -156,40 +190,29 @@ operator=(consuming_buffers const& other) -> return *this; } -template -consuming_buffers:: -consuming_buffers(BufferSequence const& bs) - : bs_(bs) - , begin_(bs_.begin()) -{ - static_assert( - is_BufferSequence::value, - "BufferSequence requirements not met"); -} - -template +template inline auto -consuming_buffers:: +consuming_buffers:: begin() const -> const_iterator { return const_iterator{*this, begin_}; } -template +template inline auto -consuming_buffers:: +consuming_buffers:: end() const -> const_iterator { return const_iterator{*this, bs_.end()}; } -template +template void -consuming_buffers:: +consuming_buffers:: consume(std::size_t n) { using boost::asio::buffer_size; diff --git a/include/beast/core/impl/file_posix.ipp b/include/beast/core/impl/file_posix.ipp new file mode 100644 index 0000000000..f0a7279bb6 --- /dev/null +++ b/include/beast/core/impl/file_posix.ipp @@ -0,0 +1,331 @@ +// +// Copyright (c) 2015-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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_CORE_IMPL_FILE_POSIX_IPP +#define BEAST_CORE_IMPL_FILE_POSIX_IPP + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { + +namespace detail { + +inline +int +file_posix_close(int fd) +{ + for(;;) + { + if(! ::close(fd)) + break; + int const ev = errno; + if(errno != EINTR) + return ev; + } + return 0; +} + +} // detail + +inline +file_posix:: +~file_posix() +{ + if(fd_ != -1) + detail::file_posix_close(fd_); +} + +inline +file_posix:: +file_posix(file_posix&& other) + : fd_(other.fd_) +{ + other.fd_ = -1; +} + +inline +file_posix& +file_posix:: +operator=(file_posix&& other) +{ + if(&other == this) + return *this; + if(fd_ != -1) + detail::file_posix_close(fd_); + fd_ = other.fd_; + other.fd_ = -1; + return *this; +} + +inline +void +file_posix:: +native_handle(native_handle_type fd) +{ + if(fd_ != -1) + detail::file_posix_close(fd_); + fd_ = fd; +} + +inline +void +file_posix:: +close(error_code& ec) +{ + if(fd_ != -1) + { + auto const ev = + detail::file_posix_close(fd_); + if(ev) + ec.assign(ev, generic_category()); + else + ec.assign(0, ec.category()); + fd_ = -1; + } + else + { + ec.assign(0, ec.category()); + } +} + +inline +void +file_posix:: +open(char const* path, file_mode mode, error_code& ec) +{ + if(fd_ != -1) + { + auto const ev = + detail::file_posix_close(fd_); + if(ev) + ec.assign(ev, generic_category()); + else + ec.assign(0, ec.category()); + fd_ = -1; + } + int f = 0; +#ifndef __APPLE__ + int advise = 0; +#endif + switch(mode) + { + default: + case file_mode::read: + f = O_RDONLY; + #ifndef __APPLE__ + advise = POSIX_FADV_RANDOM; + #endif + break; + case file_mode::scan: + f = O_RDONLY; + #ifndef __APPLE__ + advise = POSIX_FADV_SEQUENTIAL; + #endif + break; + + case file_mode::write: + f = O_RDWR | O_CREAT | O_TRUNC; + #ifndef __APPLE__ + advise = POSIX_FADV_RANDOM; + #endif + break; + + case file_mode::write_new: + f = O_RDWR | O_CREAT | O_EXCL; + #ifndef __APPLE__ + advise = POSIX_FADV_RANDOM; + #endif + break; + + case file_mode::write_existing: + f = O_RDWR | O_EXCL; + #ifndef __APPLE__ + advise = POSIX_FADV_RANDOM; + #endif + break; + + case file_mode::append: + f = O_RDWR | O_CREAT | O_TRUNC; + #ifndef __APPLE__ + advise = POSIX_FADV_SEQUENTIAL; + #endif + break; + + case file_mode::append_new: + f = O_RDWR | O_CREAT | O_EXCL; + #ifndef __APPLE__ + advise = POSIX_FADV_SEQUENTIAL; + #endif + break; + + case file_mode::append_existing: + f = O_RDWR | O_EXCL; + #ifndef __APPLE__ + advise = POSIX_FADV_SEQUENTIAL; + #endif + break; + } + for(;;) + { + fd_ = ::open(path, f, 0644); + if(fd_ != -1) + break; + auto const ev = errno; + if(ev != EINTR) + { + ec.assign(ev, generic_category()); + return; + } + } +#ifndef __APPLE__ + if(::posix_fadvise(fd_, 0, 0, advise)) + { + auto const ev = errno; + detail::file_posix_close(fd_); + fd_ = -1; + ec.assign(ev, generic_category()); + return; + } +#endif + ec.assign(0, ec.category()); +} + +inline +std::uint64_t +file_posix:: +size(error_code& ec) const +{ + if(fd_ == -1) + { + ec.assign(errc::invalid_argument, generic_category()); + return 0; + } + struct stat st; + if(::fstat(fd_, &st) != 0) + { + ec.assign(errno, generic_category()); + return 0; + } + ec.assign(0, ec.category()); + return st.st_size; +} + +inline +std::uint64_t +file_posix:: +pos(error_code& ec) const +{ + if(fd_ == -1) + { + ec.assign(errc::invalid_argument, generic_category()); + return 0; + } + auto const result = ::lseek(fd_, 0, SEEK_CUR); + if(result == (off_t)-1) + { + ec.assign(errno, generic_category()); + return 0; + } + ec.assign(0, ec.category()); + return result; +} + +inline +void +file_posix:: +seek(std::uint64_t offset, error_code& ec) +{ + if(fd_ == -1) + { + ec.assign(errc::invalid_argument, generic_category()); + return; + } + auto const result = ::lseek(fd_, offset, SEEK_SET); + if(result == static_cast(-1)) + { + ec.assign(errno, generic_category()); + return; + } + ec.assign(0, ec.category()); +} + +inline +std::size_t +file_posix:: +read(void* buffer, std::size_t n, error_code& ec) const +{ + if(fd_ == -1) + { + ec.assign(errc::invalid_argument, generic_category()); + return 0; + } + std::size_t nread = 0; + while(n > 0) + { + auto const amount = static_cast((std::min)( + n, static_cast(SSIZE_MAX))); + auto const result = ::read(fd_, buffer, amount); + if(result == -1) + { + auto const ev = errno; + if(ev == EINTR) + continue; + ec.assign(ev, generic_category()); + return nread; + } + if(result == 0) + { + // short read + return nread; + } + n -= result; + nread += result; + buffer = reinterpret_cast(buffer) + result; + } + return nread; +} + +inline +std::size_t +file_posix:: +write(void const* buffer, std::size_t n, error_code& ec) +{ + if(fd_ == -1) + { + ec.assign(errc::invalid_argument, generic_category()); + return 0; + } + std::size_t nwritten = 0; + while(n > 0) + { + auto const amount = static_cast((std::min)( + n, static_cast(SSIZE_MAX))); + auto const result = ::write(fd_, buffer, amount); + if(result == -1) + { + auto const ev = errno; + if(ev == EINTR) + continue; + ec.assign(ev, generic_category()); + return nwritten; + } + n -= result; + nwritten += result; + buffer = reinterpret_cast(buffer) + result; + } + return nwritten; +} + +} // beast + +#endif diff --git a/include/beast/core/impl/file_stdio.ipp b/include/beast/core/impl/file_stdio.ipp new file mode 100644 index 0000000000..1738e48daa --- /dev/null +++ b/include/beast/core/impl/file_stdio.ipp @@ -0,0 +1,225 @@ +// +// Copyright (c) 2015-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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_CORE_IMPL_FILE_STDIO_IPP +#define BEAST_CORE_IMPL_FILE_STDIO_IPP + +#include + +namespace beast { + +inline +file_stdio:: +~file_stdio() +{ + if(f_) + fclose(f_); +} + +inline +file_stdio:: +file_stdio(file_stdio&& other) + : f_(other.f_) +{ + other.f_ = nullptr; +} + +inline +file_stdio& +file_stdio:: +operator=(file_stdio&& other) +{ + if(&other == this) + return *this; + if(f_) + fclose(f_); + f_ = other.f_; + other.f_ = nullptr; + return *this; +} + +inline +void +file_stdio:: +native_handle(FILE* f) +{ + if(f_) + fclose(f_); + f_ = f; +} + +inline +void +file_stdio:: +close(error_code& ec) +{ + if(f_) + { + int failed = fclose(f_); + f_ = nullptr; + if(failed) + { + ec.assign(errno, generic_category()); + return; + } + } + ec.assign(0, ec.category()); +} + +inline +void +file_stdio:: +open(char const* path, file_mode mode, error_code& ec) +{ + if(f_) + { + fclose(f_); + f_ = nullptr; + } + char const* s; + switch(mode) + { + default: + case file_mode::read: s = "rb"; break; + case file_mode::scan: s = "rb"; break; + case file_mode::write: s = "wb"; break; + case file_mode::write_new: s = "wbx"; break; + case file_mode::write_existing: s = "wb"; break; + case file_mode::append: s = "ab"; break; + case file_mode::append_new: s = "abx"; break; + case file_mode::append_existing: s = "ab"; break; + } + f_ = std::fopen(path, s); + if(! f_) + { + ec.assign(errno, generic_category()); + return; + } + ec.assign(0, ec.category()); +} + +inline +std::uint64_t +file_stdio:: +size(error_code& ec) const +{ + if(! f_) + { + ec.assign(errc::invalid_argument, generic_category()); + return 0; + } + long pos = std::ftell(f_); + if(pos == -1L) + { + ec.assign(errno, generic_category()); + return 0; + } + int result = std::fseek(f_, 0, SEEK_END); + if(result != 0) + { + ec.assign(errno, generic_category()); + return 0; + } + long size = std::ftell(f_); + if(size == -1L) + { + ec.assign(errno, generic_category()); + std::fseek(f_, pos, SEEK_SET); + return 0; + } + result = std::fseek(f_, pos, SEEK_SET); + if(result != 0) + ec.assign(errno, generic_category()); + else + ec.assign(0, ec.category()); + return size; +} + +inline +std::uint64_t +file_stdio:: +pos(error_code& ec) const +{ + if(! f_) + { + ec.assign(errc::invalid_argument, generic_category()); + return 0; + } + long pos = std::ftell(f_); + if(pos == -1L) + { + ec.assign(errno, generic_category()); + return 0; + } + ec.assign(0, ec.category()); + return pos; +} + +inline +void +file_stdio:: +seek(std::uint64_t offset, error_code& ec) +{ + if(! f_) + { + ec.assign(errc::invalid_argument, generic_category()); + return; + } + if(offset > (std::numeric_limits::max)()) + { + ec = make_error_code(errc::invalid_seek); + return; + } + int result = std::fseek(f_, + static_cast(offset), SEEK_SET); + if(result != 0) + ec.assign(errno, generic_category()); + else + ec.assign(0, ec.category()); +} + +inline +std::size_t +file_stdio:: +read(void* buffer, std::size_t n, error_code& ec) const +{ + if(! f_) + { + ec.assign(errc::invalid_argument, generic_category()); + return 0; + } + auto nread = std::fread(buffer, 1, n, f_); + if(std::ferror(f_)) + { + ec.assign(errno, generic_category()); + return 0; + } + return nread; +} + +inline +std::size_t +file_stdio:: +write(void const* buffer, std::size_t n, error_code& ec) +{ + if(! f_) + { + ec.assign(errc::invalid_argument, generic_category()); + return 0; + } + auto nwritten = std::fwrite(buffer, 1, n, f_); + if(std::ferror(f_)) + { + ec.assign(errno, generic_category()); + return 0; + } + return nwritten; +} + +} // beast + +#endif diff --git a/include/beast/core/impl/file_win32.ipp b/include/beast/core/impl/file_win32.ipp new file mode 100644 index 0000000000..5b7a16470f --- /dev/null +++ b/include/beast/core/impl/file_win32.ipp @@ -0,0 +1,356 @@ +// +// Copyright (c) 2015-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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_CORE_IMPL_FILE_WIN32_IPP +#define BEAST_CORE_IMPL_FILE_WIN32_IPP + +#include +#include +#include +#include +#include +#include + +namespace beast { + +namespace detail { + +// VFALCO Can't seem to get boost/detail/winapi to work with +// this so use the non-Ex version for now. +inline +boost::detail::winapi::BOOL_ +set_file_pointer_ex( + boost::detail::winapi::HANDLE_ hFile, + boost::detail::winapi::LARGE_INTEGER_ lpDistanceToMove, + boost::detail::winapi::PLARGE_INTEGER_ lpNewFilePointer, + boost::detail::winapi::DWORD_ dwMoveMethod) +{ + auto dwHighPart = lpDistanceToMove.u.HighPart; + auto dwLowPart = boost::detail::winapi::SetFilePointer( + hFile, + lpDistanceToMove.u.LowPart, + &dwHighPart, + dwMoveMethod); + if(dwLowPart == boost::detail::winapi::INVALID_SET_FILE_POINTER_) + return 0; + if(lpNewFilePointer) + { + lpNewFilePointer->u.LowPart = dwLowPart; + lpNewFilePointer->u.HighPart = dwHighPart; + } + return 1; +} + +} // detail + +inline +file_win32:: +~file_win32() +{ + if(h_ != boost::detail::winapi::INVALID_HANDLE_VALUE_) + boost::detail::winapi::CloseHandle(h_); +} + +inline +file_win32:: +file_win32(file_win32&& other) + : h_(other.h_) +{ + other.h_ = boost::detail::winapi::INVALID_HANDLE_VALUE_; +} + +inline +file_win32& +file_win32:: +operator=(file_win32&& other) +{ + if(&other == this) + return *this; + if(h_) + boost::detail::winapi::CloseHandle(h_); + h_ = other.h_; + other.h_ = boost::detail::winapi::INVALID_HANDLE_VALUE_; + return *this; +} + +inline +void +file_win32:: +native_handle(native_handle_type h) +{ + if(h_ != boost::detail::winapi::INVALID_HANDLE_VALUE_) + boost::detail::winapi::CloseHandle(h_); + h_ = h; +} + +inline +void +file_win32:: +close(error_code& ec) +{ + if(h_ != boost::detail::winapi::INVALID_HANDLE_VALUE_) + { + if(! boost::detail::winapi::CloseHandle(h_)) + ec.assign(boost::detail::winapi::GetLastError(), + system_category()); + else + ec.assign(0, ec.category()); + h_ = boost::detail::winapi::INVALID_HANDLE_VALUE_; + } + else + { + ec.assign(0, ec.category()); + } +} + +inline +void +file_win32:: +open(char const* path, file_mode mode, error_code& ec) +{ + if(h_ != boost::detail::winapi::INVALID_HANDLE_VALUE_) + { + boost::detail::winapi::CloseHandle(h_); + h_ = boost::detail::winapi::INVALID_HANDLE_VALUE_; + } + boost::detail::winapi::DWORD_ dw1 = 0; + boost::detail::winapi::DWORD_ dw2 = 0; + boost::detail::winapi::DWORD_ dw3 = 0; +/* + | When the file... + This argument: | Exists Does not exist + -------------------------+------------------------------------------------------ + CREATE_ALWAYS | Truncates Creates + CREATE_NEW +-----------+ Fails Creates + OPEN_ALWAYS ===| does this |===> Opens Creates + OPEN_EXISTING +-----------+ Opens Fails + TRUNCATE_EXISTING | Truncates Fails +*/ + switch(mode) + { + default: + case file_mode::read: + dw1 = boost::detail::winapi::GENERIC_READ_; + dw2 = boost::detail::winapi::OPEN_EXISTING_; + dw3 = 0x10000000; // FILE_FLAG_RANDOM_ACCESS + break; + + case file_mode::scan: + dw1 = boost::detail::winapi::GENERIC_READ_; + dw2 = boost::detail::winapi::OPEN_EXISTING_; + dw3 = 0x08000000; // FILE_FLAG_SEQUENTIAL_SCAN + break; + + case file_mode::write: + dw1 = boost::detail::winapi::GENERIC_READ_ | + boost::detail::winapi::GENERIC_WRITE_; + dw2 = boost::detail::winapi::CREATE_ALWAYS_; + dw3 = 0x10000000; // FILE_FLAG_RANDOM_ACCESS + break; + + case file_mode::write_new: + dw1 = boost::detail::winapi::GENERIC_READ_ | + boost::detail::winapi::GENERIC_WRITE_; + dw2 = boost::detail::winapi::CREATE_NEW_; + dw3 = 0x10000000; // FILE_FLAG_RANDOM_ACCESS + break; + + case file_mode::write_existing: + dw1 = boost::detail::winapi::GENERIC_READ_ | + boost::detail::winapi::GENERIC_WRITE_; + dw2 = boost::detail::winapi::OPEN_EXISTING_; + dw3 = 0x10000000; // FILE_FLAG_RANDOM_ACCESS + break; + + case file_mode::append: + dw1 = boost::detail::winapi::GENERIC_READ_ | + boost::detail::winapi::GENERIC_WRITE_; + dw2 = boost::detail::winapi::CREATE_ALWAYS_; + dw3 = 0x08000000; // FILE_FLAG_SEQUENTIAL_SCAN + break; + + case file_mode::append_new: + dw1 = boost::detail::winapi::GENERIC_READ_ | + boost::detail::winapi::GENERIC_WRITE_; + dw2 = boost::detail::winapi::CREATE_NEW_; + dw3 = 0x08000000; // FILE_FLAG_SEQUENTIAL_SCAN + break; + + case file_mode::append_existing: + dw1 = boost::detail::winapi::GENERIC_READ_ | + boost::detail::winapi::GENERIC_WRITE_; + dw2 = boost::detail::winapi::OPEN_EXISTING_; + dw3 = 0x08000000; // FILE_FLAG_SEQUENTIAL_SCAN + break; + } + h_ = ::CreateFileA( + path, + dw1, + 0, + NULL, + dw2, + dw3, + NULL); + if(h_ == boost::detail::winapi::INVALID_HANDLE_VALUE_) + ec.assign(boost::detail::winapi::GetLastError(), + system_category()); + else + ec.assign(0, ec.category()); +} + +inline +std::uint64_t +file_win32:: +size(error_code& ec) const +{ + if(h_ == boost::detail::winapi::INVALID_HANDLE_VALUE_) + { + ec.assign(errc::invalid_argument, generic_category()); + return 0; + } + boost::detail::winapi::LARGE_INTEGER_ fileSize; + if(! boost::detail::winapi::GetFileSizeEx(h_, &fileSize)) + { + ec.assign(boost::detail::winapi::GetLastError(), + system_category()); + return 0; + } + ec.assign(0, ec.category()); + return fileSize.QuadPart; +} + +inline +std::uint64_t +file_win32:: +pos(error_code& ec) +{ + if(h_ == boost::detail::winapi::INVALID_HANDLE_VALUE_) + { + ec.assign(errc::invalid_argument, generic_category()); + return 0; + } + boost::detail::winapi::LARGE_INTEGER_ in; + boost::detail::winapi::LARGE_INTEGER_ out; + in.QuadPart = 0; + if(! detail::set_file_pointer_ex(h_, in, &out, + boost::detail::winapi::FILE_CURRENT_)) + { + ec.assign(boost::detail::winapi::GetLastError(), + system_category()); + return 0; + } + ec.assign(0, ec.category()); + return out.QuadPart; +} + +inline +void +file_win32:: +seek(std::uint64_t offset, error_code& ec) +{ + if(h_ == boost::detail::winapi::INVALID_HANDLE_VALUE_) + { + ec.assign(errc::invalid_argument, generic_category()); + return; + } + boost::detail::winapi::LARGE_INTEGER_ in; + in.QuadPart = offset; + if(! detail::set_file_pointer_ex(h_, in, 0, + boost::detail::winapi::FILE_BEGIN_)) + { + ec.assign(boost::detail::winapi::GetLastError(), + system_category()); + return; + } + ec.assign(0, ec.category()); +} + +inline +std::size_t +file_win32:: +read(void* buffer, std::size_t n, error_code& ec) +{ + if(h_ == boost::detail::winapi::INVALID_HANDLE_VALUE_) + { + ec.assign(errc::invalid_argument, generic_category()); + return 0; + } + std::size_t nread = 0; + while(n > 0) + { + boost::detail::winapi::DWORD_ amount; + if(n > (std::numeric_limits< + boost::detail::winapi::DWORD_>::max)()) + amount = (std::numeric_limits< + boost::detail::winapi::DWORD_>::max)(); + else + amount = static_cast< + boost::detail::winapi::DWORD_>(n); + boost::detail::winapi::DWORD_ bytesRead; + if(! ::ReadFile(h_, buffer, amount, &bytesRead, 0)) + { + auto const dwError = ::GetLastError(); + if(dwError != boost::detail::winapi::ERROR_HANDLE_EOF_) + ec.assign(::GetLastError(), system_category()); + else + ec.assign(0, ec.category()); + return nread; + } + if(bytesRead == 0) + return nread; + n -= bytesRead; + nread += bytesRead; + buffer = reinterpret_cast(buffer) + bytesRead; + } + ec.assign(0, ec.category()); + return nread; +} + +inline +std::size_t +file_win32:: +write(void const* buffer, std::size_t n, error_code& ec) +{ + if(h_ == boost::detail::winapi::INVALID_HANDLE_VALUE_) + { + ec.assign(errc::invalid_argument, generic_category()); + return 0; + } + std::size_t nwritten = 0; + while(n > 0) + { + boost::detail::winapi::DWORD_ amount; + if(n > (std::numeric_limits< + boost::detail::winapi::DWORD_>::max)()) + amount = (std::numeric_limits< + boost::detail::winapi::DWORD_>::max)(); + else + amount = static_cast< + boost::detail::winapi::DWORD_>(n); + boost::detail::winapi::DWORD_ bytesWritten; + if(! ::WriteFile(h_, buffer, amount, &bytesWritten, 0)) + { + auto const dwError = ::GetLastError(); + if(dwError != boost::detail::winapi::ERROR_HANDLE_EOF_) + ec.assign(::GetLastError(), system_category()); + else + ec.assign(0, ec.category()); + return nwritten; + } + if(bytesWritten == 0) + return nwritten; + n -= bytesWritten; + nwritten += bytesWritten; + buffer = reinterpret_cast(buffer) + bytesWritten; + } + ec.assign(0, ec.category()); + return nwritten; +} + +} // beast + +#endif diff --git a/include/beast/core/impl/flat_buffer.ipp b/include/beast/core/impl/flat_buffer.ipp new file mode 100644 index 0000000000..b7a938ed1d --- /dev/null +++ b/include/beast/core/impl/flat_buffer.ipp @@ -0,0 +1,471 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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_IMPL_FLAT_BUFFER_HPP +#define BEAST_IMPL_FLAT_BUFFER_HPP + +#include +#include +#include + +namespace beast { + +/* Memory is laid out thusly: + + begin_ ..|.. in_ ..|.. out_ ..|.. last_ ..|.. end_ +*/ + +template +basic_flat_buffer:: +~basic_flat_buffer() +{ + if(begin_) + alloc_traits::deallocate( + this->member(), begin_, dist(begin_, end_)); +} + +template +basic_flat_buffer:: +basic_flat_buffer() + : begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_((std::numeric_limits::max)()) +{ +} + +template +basic_flat_buffer:: +basic_flat_buffer(std::size_t limit) + : begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(limit) +{ +} + +template +basic_flat_buffer:: +basic_flat_buffer(Allocator const& alloc) + : detail::empty_base_optimization(alloc) + , begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_((std::numeric_limits::max)()) +{ +} + +template +basic_flat_buffer:: +basic_flat_buffer(std::size_t limit, Allocator const& alloc) + : detail::empty_base_optimization(alloc) + , begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(limit) +{ +} + +template +basic_flat_buffer:: +basic_flat_buffer(basic_flat_buffer&& other) + : detail::empty_base_optimization( + std::move(other.member())) + , begin_(other.begin_) + , in_(other.in_) + , out_(other.out_) + , last_(out_) + , end_(other.end_) + , max_(other.max_) +{ + other.begin_ = nullptr; + other.in_ = nullptr; + other.out_ = nullptr; + other.last_ = nullptr; + other.end_ = nullptr; +} + +template +basic_flat_buffer:: +basic_flat_buffer(basic_flat_buffer&& other, + Allocator const& alloc) + : detail::empty_base_optimization(alloc) +{ + if(this->member() != other.member()) + { + begin_ = nullptr; + in_ = nullptr; + out_ = nullptr; + last_ = nullptr; + end_ = nullptr; + max_ = other.max_; + copy_from(other); + other.reset(); + } + else + { + begin_ = other.begin_; + in_ = other.in_; + out_ = other.out_; + last_ = out_; + end_ = other.end_; + max_ = other.max_; + other.begin_ = nullptr; + other.in_ = nullptr; + other.out_ = nullptr; + other.last_ = nullptr; + other.end_ = nullptr; + } +} + +template +basic_flat_buffer:: +basic_flat_buffer(basic_flat_buffer const& other) + : detail::empty_base_optimization( + alloc_traits::select_on_container_copy_construction( + other.member())) + , begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(other.max_) +{ + copy_from(other); +} + +template +basic_flat_buffer:: +basic_flat_buffer(basic_flat_buffer const& other, + Allocator const& alloc) + : detail::empty_base_optimization(alloc) + , begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(other.max_) +{ + copy_from(other); +} + +template +template +basic_flat_buffer:: +basic_flat_buffer( + basic_flat_buffer const& other) + : begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(other.max_) +{ + copy_from(other); +} + +template +template +basic_flat_buffer:: +basic_flat_buffer(basic_flat_buffer const& other, + Allocator const& alloc) + : detail::empty_base_optimization(alloc) + , begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(other.max_) +{ + copy_from(other); +} + +template +auto +basic_flat_buffer:: +operator=(basic_flat_buffer&& other) -> + basic_flat_buffer& +{ + if(this != &other) + move_assign(other, + typename alloc_traits::propagate_on_container_move_assignment{}); + return *this; +} + +template +auto +basic_flat_buffer:: +operator=(basic_flat_buffer const& other) -> + basic_flat_buffer& +{ + if(this != &other) + copy_assign(other, + typename alloc_traits::propagate_on_container_copy_assignment{}); + return *this; +} + +template +template +auto +basic_flat_buffer:: +operator=(basic_flat_buffer const& other) -> + basic_flat_buffer& +{ + reset(); + max_ = other.max_; + copy_from(other); + return *this; +} + +//------------------------------------------------------------------------------ + +template +auto +basic_flat_buffer:: +prepare(std::size_t n) -> + mutable_buffers_type +{ + if(n <= dist(out_, end_)) + { + // existing capacity is sufficient + last_ = out_ + n; + return{out_, n}; + } + auto const len = size(); + if(n <= capacity() - len) + { + // after a memmove, + // existing capacity is sufficient + if(len > 0) + std::memmove(begin_, in_, len); + in_ = begin_; + out_ = in_ + len; + last_ = out_ + n; + return {out_, n}; + } + // enforce maximum capacity + if(n > max_ - len) + BOOST_THROW_EXCEPTION(std::length_error{ + "basic_flat_buffer overflow"}); + // allocate a new buffer + auto const new_size = std::min( + max_, + std::max(2 * len, len + n)); + auto const p = alloc_traits::allocate( + this->member(), new_size); + if(begin_) + { + BOOST_ASSERT(p); + BOOST_ASSERT(in_); + std::memcpy(p, in_, len); + alloc_traits::deallocate( + this->member(), begin_, capacity()); + } + begin_ = p; + in_ = begin_; + out_ = in_ + len; + last_ = out_ + n; + end_ = begin_ + new_size; + return {out_, n}; +} + +template +void +basic_flat_buffer:: +consume(std::size_t n) +{ + if(n >= dist(in_, out_)) + { + in_ = begin_; + out_ = begin_; + return; + } + in_ += n; +} + +template +void +basic_flat_buffer:: +shrink_to_fit() +{ + auto const len = size(); + if(len == capacity()) + return; + char* p; + if(len > 0) + { + BOOST_ASSERT(begin_); + BOOST_ASSERT(in_); + p = alloc_traits::allocate( + this->member(), len); + std::memcpy(p, in_, len); + } + else + { + p = nullptr; + } + alloc_traits::deallocate( + this->member(), begin_, dist(begin_, end_)); + begin_ = p; + in_ = begin_; + out_ = begin_ + len; + last_ = out_; + end_ = out_; +} + +//------------------------------------------------------------------------------ + +template +inline +void +basic_flat_buffer:: +reset() +{ + consume(size()); + shrink_to_fit(); +} + +template +template +inline +void +basic_flat_buffer:: +copy_from(DynamicBuffer const& buffer) +{ + if(buffer.size() == 0) + return; + using boost::asio::buffer_copy; + commit(buffer_copy( + prepare(buffer.size()), buffer.data())); +} + +template +inline +void +basic_flat_buffer:: +move_assign(basic_flat_buffer& other, std::true_type) +{ + reset(); + this->member() = std::move(other.member()); + begin_ = other.begin_; + in_ = other.in_; + out_ = other.out_; + last_ = out_; + end_ = other.end_; + max_ = other.max_; + other.begin_ = nullptr; + other.in_ = nullptr; + other.out_ = nullptr; + other.last_ = nullptr; + other.end_ = nullptr; +} + +template +inline +void +basic_flat_buffer:: +move_assign(basic_flat_buffer& other, std::false_type) +{ + reset(); + if(this->member() != other.member()) + { + copy_from(other); + other.reset(); + } + else + { + move_assign(other, std::true_type{}); + } +} + +template +inline +void +basic_flat_buffer:: +copy_assign(basic_flat_buffer const& other, std::true_type) +{ + reset(); + max_ = other.max_; + this->member() = other.member(); + copy_from(other); +} + +template +inline +void +basic_flat_buffer:: +copy_assign(basic_flat_buffer const& other, std::false_type) +{ + reset(); + max_ = other.max_; + copy_from(other); +} + +template +inline +void +basic_flat_buffer:: +swap(basic_flat_buffer& other) +{ + swap(other, typename + alloc_traits::propagate_on_container_swap{}); +} + +template +inline +void +basic_flat_buffer:: +swap(basic_flat_buffer& other, std::true_type) +{ + using std::swap; + swap(this->member(), other.member()); + swap(max_, other.max_); + swap(begin_, other.begin_); + swap(in_, other.in_); + swap(out_, other.out_); + last_ = this->out_; + other.last_ = other.out_; + swap(end_, other.end_); +} + +template +inline +void +basic_flat_buffer:: +swap(basic_flat_buffer& other, std::false_type) +{ + BOOST_ASSERT(this->member() == other.member()); + using std::swap; + swap(max_, other.max_); + swap(begin_, other.begin_); + swap(in_, other.in_); + swap(out_, other.out_); + last_ = this->out_; + other.last_ = other.out_; + swap(end_, other.end_); +} + +template +void +swap( + basic_flat_buffer& lhs, + basic_flat_buffer& rhs) +{ + lhs.swap(rhs); +} + +} // beast + +#endif diff --git a/include/beast/core/impl/handler_ptr.ipp b/include/beast/core/impl/handler_ptr.ipp index 2243d5df34..a882acf9db 100644 --- a/include/beast/core/impl/handler_ptr.ipp +++ b/include/beast/core/impl/handler_ptr.ipp @@ -8,8 +8,9 @@ #ifndef BEAST_IMPL_HANDLER_PTR_HPP #define BEAST_IMPL_HANDLER_PTR_HPP -#include -#include +#include +#include +#include #include #include @@ -23,9 +24,10 @@ P(DeducedHandler&& h, Args&&... args) : n(1) , handler(std::forward(h)) { + using boost::asio::asio_handler_allocate; t = reinterpret_cast( - beast_asio_helpers:: - allocate(sizeof(T), handler)); + asio_handler_allocate( + sizeof(T), std::addressof(handler))); try { t = new(t) T{handler, @@ -33,8 +35,9 @@ P(DeducedHandler&& h, Args&&... args) } catch(...) { - beast_asio_helpers:: - deallocate(t, sizeof(T), handler); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + t, sizeof(T), std::addressof(handler)); throw; } } @@ -50,8 +53,9 @@ handler_ptr:: if(p_->t) { p_->t->~T(); - beast_asio_helpers:: - deallocate(p_->t, sizeof(T), p_->handler); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p_->t, sizeof(T), std::addressof(p_->handler)); } delete p_; } @@ -80,8 +84,7 @@ handler_ptr(Handler&& handler, Args&&... args) : p_(new P{std::move(handler), std::forward(args)...}) { - static_assert(! std::is_array::value, - "T must not be an array type"); + BOOST_STATIC_ASSERT(! std::is_array::value); } template @@ -90,8 +93,7 @@ handler_ptr:: handler_ptr(Handler const& handler, Args&&... args) : p_(new P{handler, std::forward(args)...}) { - static_assert(! std::is_array::value, - "T must not be an array type"); + BOOST_STATIC_ASSERT(! std::is_array::value); } template @@ -103,8 +105,9 @@ release_handler() -> BOOST_ASSERT(p_); BOOST_ASSERT(p_->t); p_->t->~T(); - beast_asio_helpers:: - deallocate(p_->t, sizeof(T), p_->handler); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p_->t, sizeof(T), std::addressof(p_->handler)); p_->t = nullptr; return std::move(p_->handler); } @@ -118,8 +121,9 @@ invoke(Args&&... args) BOOST_ASSERT(p_); BOOST_ASSERT(p_->t); p_->t->~T(); - beast_asio_helpers:: - deallocate(p_->t, sizeof(T), p_->handler); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p_->t, sizeof(T), std::addressof(p_->handler)); p_->t = nullptr; p_->handler(std::forward(args)...); } diff --git a/include/beast/core/impl/streambuf.ipp b/include/beast/core/impl/multi_buffer.ipp similarity index 60% rename from include/beast/core/impl/streambuf.ipp rename to include/beast/core/impl/multi_buffer.ipp index ea39d76389..aed523a43c 100644 --- a/include/beast/core/impl/streambuf.ipp +++ b/include/beast/core/impl/multi_buffer.ipp @@ -5,12 +5,12 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_IMPL_STREAMBUF_IPP -#define BEAST_IMPL_STREAMBUF_IPP +#ifndef BEAST_IMPL_MULTI_BUFFER_IPP +#define BEAST_IMPL_MULTI_BUFFER_IPP #include -#include #include +#include #include #include #include @@ -84,7 +84,7 @@ namespace beast { */ template -class basic_streambuf::element +class basic_multi_buffer::element : public boost::intrusive::list_base_hook< boost::intrusive::link_mode< boost::intrusive::normal_link>> @@ -118,14 +118,14 @@ public: }; template -class basic_streambuf::const_buffers_type +class basic_multi_buffer::const_buffers_type { - basic_streambuf const* sb_; + basic_multi_buffer const* b_; - friend class basic_streambuf; + friend class basic_multi_buffer; explicit - const_buffers_type(basic_streambuf const& sb); + const_buffers_type(basic_multi_buffer const& b); public: // Why? @@ -142,17 +142,24 @@ public: const_iterator end() const; + + friend + std::size_t + buffer_size(const_buffers_type const& buffers) + { + return buffers.b_->size(); + } }; template -class basic_streambuf::mutable_buffers_type +class basic_multi_buffer::mutable_buffers_type { - basic_streambuf const* sb_; + basic_multi_buffer const* b_; - friend class basic_streambuf; + friend class basic_multi_buffer; explicit - mutable_buffers_type(basic_streambuf const& sb); + mutable_buffers_type(basic_multi_buffer const& b); public: using value_type = mutable_buffer; @@ -173,9 +180,9 @@ public: //------------------------------------------------------------------------------ template -class basic_streambuf::const_buffers_type::const_iterator +class basic_multi_buffer::const_buffers_type::const_iterator { - basic_streambuf const* sb_ = nullptr; + basic_multi_buffer const* b_ = nullptr; typename list_type::const_iterator it_; public: @@ -193,9 +200,9 @@ public: const_iterator& operator=(const_iterator&& other) = default; const_iterator& operator=(const_iterator const& other) = default; - const_iterator(basic_streambuf const& sb, + const_iterator(basic_multi_buffer const& b, typename list_type::const_iterator const& it) - : sb_(&sb) + : b_(&b) , it_(it) { } @@ -203,7 +210,7 @@ public: bool operator==(const_iterator const& other) const { - return sb_ == other.sb_ && it_ == other.it_; + return b_ == other.b_ && it_ == other.it_; } bool @@ -217,9 +224,9 @@ public: { auto const& e = *it_; return value_type{e.data(), - (sb_->out_ == sb_->list_.end() || - &e != &*sb_->out_) ? e.size() : sb_->out_pos_} + - (&e == &*sb_->list_.begin() ? sb_->in_pos_ : 0); + (b_->out_ == b_->list_.end() || + &e != &*b_->out_) ? e.size() : b_->out_pos_} + + (&e == &*b_->list_.begin() ? b_->in_pos_ : 0); } pointer @@ -257,36 +264,42 @@ public: }; template -basic_streambuf::const_buffers_type::const_buffers_type( - basic_streambuf const& sb) - : sb_(&sb) +basic_multi_buffer:: +const_buffers_type:: +const_buffers_type( + basic_multi_buffer const& b) + : b_(&b) { } template auto -basic_streambuf::const_buffers_type::begin() const -> +basic_multi_buffer:: +const_buffers_type:: +begin() const -> const_iterator { - return const_iterator{*sb_, sb_->list_.begin()}; + return const_iterator{*b_, b_->list_.begin()}; } template auto -basic_streambuf::const_buffers_type::end() const -> +basic_multi_buffer:: +const_buffers_type:: +end() const -> const_iterator { - return const_iterator{*sb_, sb_->out_ == - sb_->list_.end() ? sb_->list_.end() : - std::next(sb_->out_)}; + return const_iterator{*b_, b_->out_ == + b_->list_.end() ? b_->list_.end() : + std::next(b_->out_)}; } //------------------------------------------------------------------------------ template -class basic_streambuf::mutable_buffers_type::const_iterator +class basic_multi_buffer::mutable_buffers_type::const_iterator { - basic_streambuf const* sb_ = nullptr; + basic_multi_buffer const* b_ = nullptr; typename list_type::const_iterator it_; public: @@ -304,9 +317,9 @@ public: const_iterator& operator=(const_iterator&& other) = default; const_iterator& operator=(const_iterator const& other) = default; - const_iterator(basic_streambuf const& sb, + const_iterator(basic_multi_buffer const& b, typename list_type::const_iterator const& it) - : sb_(&sb) + : b_(&b) , it_(it) { } @@ -314,7 +327,7 @@ public: bool operator==(const_iterator const& other) const { - return sb_ == other.sb_ && it_ == other.it_; + return b_ == other.b_ && it_ == other.it_; } bool @@ -328,9 +341,9 @@ public: { auto const& e = *it_; return value_type{e.data(), - &e == &*std::prev(sb_->list_.end()) ? - sb_->out_end_ : e.size()} + - (&e == &*sb_->out_ ? sb_->out_pos_ : 0); + &e == &*std::prev(b_->list_.end()) ? + b_->out_end_ : e.size()} + + (&e == &*b_->out_ ? b_->out_pos_ : 0); } pointer @@ -368,42 +381,84 @@ public: }; template -basic_streambuf::mutable_buffers_type::mutable_buffers_type( - basic_streambuf const& sb) - : sb_(&sb) +basic_multi_buffer:: +mutable_buffers_type:: +mutable_buffers_type( + basic_multi_buffer const& b) + : b_(&b) { } template auto -basic_streambuf::mutable_buffers_type::begin() const -> +basic_multi_buffer:: +mutable_buffers_type:: +begin() const -> const_iterator { - return const_iterator{*sb_, sb_->out_}; + return const_iterator{*b_, b_->out_}; } template auto -basic_streambuf::mutable_buffers_type::end() const -> +basic_multi_buffer:: +mutable_buffers_type:: +end() const -> const_iterator { - return const_iterator{*sb_, sb_->list_.end()}; + return const_iterator{*b_, b_->list_.end()}; } //------------------------------------------------------------------------------ template -basic_streambuf::~basic_streambuf() +basic_multi_buffer:: +~basic_multi_buffer() { delete_list(); } template -basic_streambuf:: -basic_streambuf(basic_streambuf&& other) +basic_multi_buffer:: +basic_multi_buffer() + : out_(list_.end()) +{ +} + +template +basic_multi_buffer:: +basic_multi_buffer(std::size_t limit) + : max_(limit) + , out_(list_.end()) +{ +} + +template +basic_multi_buffer:: +basic_multi_buffer(Allocator const& alloc) + : detail::empty_base_optimization< + allocator_type>(alloc) + , out_(list_.end()) +{ +} + +template +basic_multi_buffer:: +basic_multi_buffer(std::size_t limit, + Allocator const& alloc) + : detail::empty_base_optimization< + allocator_type>(alloc) + , max_(limit) + , out_(list_.end()) +{ +} + +template +basic_multi_buffer:: +basic_multi_buffer(basic_multi_buffer&& other) : detail::empty_base_optimization( std::move(other.member())) - , alloc_size_(other.alloc_size_) + , max_(other.max_) , in_size_(other.in_size_) , in_pos_(other.in_pos_) , out_pos_(other.out_pos_) @@ -421,116 +476,127 @@ basic_streambuf(basic_streambuf&& other) } template -basic_streambuf:: -basic_streambuf(basic_streambuf&& other, - allocator_type const& alloc) - : basic_streambuf(other.alloc_size_, alloc) -{ - using boost::asio::buffer_copy; - if(this->member() != other.member()) - commit(buffer_copy(prepare(other.size()), other.data())); - else - move_assign(other, std::true_type{}); -} - -template -auto -basic_streambuf::operator=( - basic_streambuf&& other) -> basic_streambuf& -{ - if(this == &other) - return *this; - // VFALCO If any memory allocated we could use it first? - clear(); - alloc_size_ = other.alloc_size_; - move_assign(other, std::integral_constant{}); - return *this; -} - -template -basic_streambuf:: -basic_streambuf(basic_streambuf const& other) - : basic_streambuf(other.alloc_size_, - alloc_traits::select_on_container_copy_construction(other.member())) -{ - commit(boost::asio::buffer_copy(prepare(other.size()), other.data())); -} - -template -basic_streambuf:: -basic_streambuf(basic_streambuf const& other, - allocator_type const& alloc) - : basic_streambuf(other.alloc_size_, alloc) -{ - commit(boost::asio::buffer_copy(prepare(other.size()), other.data())); -} - -template -auto -basic_streambuf::operator=( - basic_streambuf const& other) -> - basic_streambuf& -{ - if(this == &other) - return *this; - using boost::asio::buffer_copy; - clear(); - copy_assign(other, std::integral_constant{}); - commit(buffer_copy(prepare(other.size()), other.data())); - return *this; -} - -template -template -basic_streambuf::basic_streambuf( - basic_streambuf const& other) - : basic_streambuf(other.alloc_size_) -{ - using boost::asio::buffer_copy; - commit(buffer_copy(prepare(other.size()), other.data())); -} - -template -template -basic_streambuf::basic_streambuf( - basic_streambuf const& other, - allocator_type const& alloc) - : basic_streambuf(other.alloc_size_, alloc) -{ - using boost::asio::buffer_copy; - commit(buffer_copy(prepare(other.size()), other.data())); -} - -template -template -auto -basic_streambuf::operator=( - basic_streambuf const& other) -> - basic_streambuf& -{ - using boost::asio::buffer_copy; - clear(); - commit(buffer_copy(prepare(other.size()), other.data())); - return *this; -} - -template -basic_streambuf::basic_streambuf( - std::size_t alloc_size, Allocator const& alloc) +basic_multi_buffer:: +basic_multi_buffer(basic_multi_buffer&& other, + Allocator const& alloc) : detail::empty_base_optimization(alloc) - , out_(list_.end()) - , alloc_size_(alloc_size) + , max_(other.max_) { - if(alloc_size <= 0) - throw detail::make_exception( - "invalid alloc_size", __FILE__, __LINE__); + if(this->member() != other.member()) + { + out_ = list_.end(); + copy_from(other); + other.reset(); + } + else + { + auto const at_end = + other.out_ == other.list_.end(); + list_ = std::move(other.list_); + out_ = at_end ? list_.end() : other.out_; + in_size_ = other.in_size_; + in_pos_ = other.in_pos_; + out_pos_ = other.out_pos_; + out_end_ = other.out_end_; + other.in_size_ = 0; + other.out_ = other.list_.end(); + other.in_pos_ = 0; + other.out_pos_ = 0; + other.out_end_ = 0; + } +} + +template +basic_multi_buffer:: +basic_multi_buffer(basic_multi_buffer const& other) + : detail::empty_base_optimization( + alloc_traits::select_on_container_copy_construction(other.member())) + , max_(other.max_) + , out_(list_.end()) +{ + copy_from(other); +} + +template +basic_multi_buffer:: +basic_multi_buffer(basic_multi_buffer const& other, + Allocator const& alloc) + : detail::empty_base_optimization(alloc) + , max_(other.max_) + , out_(list_.end()) +{ + copy_from(other); +} + +template +template +basic_multi_buffer:: +basic_multi_buffer( + basic_multi_buffer const& other) + : out_(list_.end()) +{ + copy_from(other); +} + +template +template +basic_multi_buffer:: +basic_multi_buffer( + basic_multi_buffer const& other, + allocator_type const& alloc) + : detail::empty_base_optimization(alloc) + , max_(other.max_) + , out_(list_.end()) +{ + copy_from(other); +} + +template +auto +basic_multi_buffer:: +operator=(basic_multi_buffer&& other) -> + basic_multi_buffer& +{ + if(this == &other) + return *this; + reset(); + max_ = other.max_; + move_assign(other, typename + alloc_traits::propagate_on_container_move_assignment{}); + return *this; +} + +template +auto +basic_multi_buffer:: +operator=(basic_multi_buffer const& other) -> +basic_multi_buffer& +{ + if(this == &other) + return *this; + copy_assign(other, typename + alloc_traits::propagate_on_container_copy_assignment{}); + return *this; +} + +template +template +auto +basic_multi_buffer:: +operator=( + basic_multi_buffer const& other) -> + basic_multi_buffer& +{ + reset(); + max_ = other.max_; + copy_from(other); + return *this; } template std::size_t -basic_streambuf::capacity() const +basic_multi_buffer:: +capacity() const { auto pos = out_; if(pos == list_.end()) @@ -543,7 +609,7 @@ basic_streambuf::capacity() const template auto -basic_streambuf:: +basic_multi_buffer:: data() const -> const_buffers_type { @@ -552,18 +618,27 @@ data() const -> template auto -basic_streambuf::prepare(size_type n) -> +basic_multi_buffer:: +prepare(size_type n) -> mutable_buffers_type { + if(in_size_ + n > max_) + BOOST_THROW_EXCEPTION(std::length_error{ + "dynamic buffer overflow"}); list_type reuse; + std::size_t total = in_size_; + // put all empty buffers on reuse list if(out_ != list_.end()) { + total += out_->size() - out_pos_; if(out_ != list_.iterator_to(list_.back())) { out_end_ = out_->size(); reuse.splice(reuse.end(), list_, std::next(out_), list_.end()); + #if BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); + #endif } auto const avail = out_->size() - out_pos_; if(n > avail) @@ -576,13 +651,17 @@ basic_streambuf::prepare(size_type n) -> out_end_ = out_pos_ + n; n = 0; } + #if BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); + #endif } + // get space from reuse buffers while(n > 0 && ! reuse.empty()) { auto& e = reuse.front(); reuse.erase(reuse.iterator_to(e)); list_.push_back(e); + total += e.size(); if(n > e.size()) { out_end_ = e.size(); @@ -593,11 +672,28 @@ basic_streambuf::prepare(size_type n) -> out_end_ = n; n = 0; } + #if BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); + #endif } - while(n > 0) + BOOST_ASSERT(total <= max_); + for(auto it = reuse.begin(); it != reuse.end();) { - auto const size = std::max(alloc_size_, n); + auto& e = *it++; + reuse.erase(list_.iterator_to(e)); + delete_element(e); + } + if(n > 0) + { + static auto const growth_factor = 2.0f; + auto const size = + std::min( + max_ - total, + std::max({ + static_cast( + in_size_ * growth_factor - in_size_), + 512, + n})); auto& e = *reinterpret_cast(static_cast< void*>(alloc_traits::allocate(this->member(), sizeof(element) + size))); @@ -605,33 +701,18 @@ basic_streambuf::prepare(size_type n) -> list_.push_back(e); if(out_ == list_.end()) out_ = list_.iterator_to(e); - if(n >= e.size()) - { - out_end_ = e.size(); - n -= e.size(); - } - else - { - out_end_ = n; - n = 0; - } + out_end_ = n; + #if BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); - } - for(auto it = reuse.begin(); it != reuse.end();) - { - auto& e = *it++; - reuse.erase(list_.iterator_to(e)); - auto const len = e.size() + sizeof(e); - alloc_traits::destroy(this->member(), &e); - alloc_traits::deallocate(this->member(), - reinterpret_cast(&e), len); + #endif } return mutable_buffers_type(*this); } template void -basic_streambuf::commit(size_type n) +basic_multi_buffer:: +commit(size_type n) { if(list_.empty()) return; @@ -647,14 +728,18 @@ basic_streambuf::commit(size_type n) { out_pos_ += n; in_size_ += n; + #if BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); + #endif return; } ++out_; n -= avail; out_pos_ = 0; in_size_ += avail; + #if BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); + #endif } n = (std::min)(n, out_end_ - out_pos_); @@ -666,26 +751,31 @@ basic_streambuf::commit(size_type n) out_pos_ = 0; out_end_ = 0; } +#if BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); +#endif } template void -basic_streambuf::consume(size_type n) +basic_multi_buffer:: +consume(size_type n) { if(list_.empty()) return; - for(;;) { if(list_.begin() != out_) { - auto const avail = list_.front().size() - in_pos_; + auto const avail = + list_.front().size() - in_pos_; if(n < avail) { in_size_ -= n; in_pos_ += n; + #if BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); + #endif break; } n -= avail; @@ -693,11 +783,10 @@ basic_streambuf::consume(size_type n) in_pos_ = 0; auto& e = list_.front(); list_.erase(list_.iterator_to(e)); - auto const len = e.size() + sizeof(e); - alloc_traits::destroy(this->member(), &e); - alloc_traits::deallocate(this->member(), - reinterpret_cast(&e), len); + delete_element(e); + #if BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); + #endif } else { @@ -724,20 +813,45 @@ basic_streambuf::consume(size_type n) out_end_ = 0; } } + #if BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); + #endif break; } } } template +inline void -basic_streambuf:: -clear() +basic_multi_buffer:: +delete_element(element& e) +{ + auto const len = sizeof(e) + e.size(); + alloc_traits::destroy(this->member(), &e); + alloc_traits::deallocate(this->member(), + reinterpret_cast(&e), len); +} + +template +inline +void +basic_multi_buffer:: +delete_list() +{ + for(auto iter = list_.begin(); iter != list_.end();) + delete_element(*iter++); +} + +template +inline +void +basic_multi_buffer:: +reset() { delete_list(); list_.clear(); - out_ = list_.begin(); + out_ = list_.end(); in_size_ = 0; in_pos_ = 0; out_pos_ = 0; @@ -745,24 +859,41 @@ clear() } template +template +inline void -basic_streambuf:: -move_assign(basic_streambuf& other, std::false_type) +basic_multi_buffer:: +copy_from(DynamicBuffer const& buffer) { + if(buffer.size() == 0) + return; using boost::asio::buffer_copy; - if(this->member() != other.member()) - { - commit(buffer_copy(prepare(other.size()), other.data())); - other.clear(); - } - else - move_assign(other, std::true_type{}); + commit(buffer_copy( + prepare(buffer.size()), buffer.data())); } template +inline void -basic_streambuf:: -move_assign(basic_streambuf& other, std::true_type) +basic_multi_buffer:: +move_assign(basic_multi_buffer& other, std::false_type) +{ + if(this->member() != other.member()) + { + copy_from(other); + other.reset(); + } + else + { + move_assign(other, std::true_type{}); + } +} + +template +inline +void +basic_multi_buffer:: +move_assign(basic_multi_buffer& other, std::true_type) { this->member() = std::move(other.member()); auto const at_end = @@ -783,38 +914,101 @@ move_assign(basic_streambuf& other, std::true_type) } template +inline void -basic_streambuf:: -copy_assign(basic_streambuf const& other, std::false_type) +basic_multi_buffer:: +copy_assign( + basic_multi_buffer const& other, std::false_type) { - beast::detail::ignore_unused(other); + reset(); + max_ = other.max_; + copy_from(other); } template +inline void -basic_streambuf:: -copy_assign(basic_streambuf const& other, std::true_type) +basic_multi_buffer:: +copy_assign( + basic_multi_buffer const& other, std::true_type) { + reset(); + max_ = other.max_; this->member() = other.member(); + copy_from(other); } template +inline void -basic_streambuf::delete_list() +basic_multi_buffer:: +swap(basic_multi_buffer& other) { - for(auto iter = list_.begin(); iter != list_.end();) - { - auto& e = *iter++; - auto const n = e.size() + sizeof(e); - alloc_traits::destroy(this->member(), &e); - alloc_traits::deallocate(this->member(), - reinterpret_cast(&e), n); - } + swap(other, typename + alloc_traits::propagate_on_container_swap{}); +} + +template +inline +void +basic_multi_buffer:: +swap(basic_multi_buffer& other, std::true_type) +{ + using std::swap; + auto const at_end0 = + out_ == list_.end(); + auto const at_end1 = + other.out_ == other.list_.end(); + swap(this->member(), other.member()); + swap(list_, other.list_); + swap(out_, other.out_); + if(at_end1) + out_ = list_.end(); + if(at_end0) + other.out_ = other.list_.end(); + swap(in_size_, other.in_size_); + swap(in_pos_, other.in_pos_); + swap(out_pos_, other.out_pos_); + swap(out_end_, other.out_end_); +} + +template +inline +void +basic_multi_buffer:: +swap(basic_multi_buffer& other, std::false_type) +{ + BOOST_ASSERT(this->member() == other.member()); + using std::swap; + auto const at_end0 = + out_ == list_.end(); + auto const at_end1 = + other.out_ == other.list_.end(); + swap(list_, other.list_); + swap(out_, other.out_); + if(at_end1) + out_ = list_.end(); + if(at_end0) + other.out_ = other.list_.end(); + swap(in_size_, other.in_size_); + swap(in_pos_, other.in_pos_); + swap(out_pos_, other.out_pos_); + swap(out_end_, other.out_end_); } template void -basic_streambuf::debug_check() const +swap( + basic_multi_buffer& lhs, + basic_multi_buffer& rhs) +{ + lhs.swap(rhs); +} + +template +void +basic_multi_buffer:: +debug_check() const { #ifndef NDEBUG using boost::asio::buffer_size; @@ -852,33 +1046,6 @@ basic_streambuf::debug_check() const #endif } -template -std::size_t -read_size_helper(basic_streambuf< - Allocator> const& streambuf, std::size_t max_size) -{ - BOOST_ASSERT(max_size >= 1); - // If we already have an allocated - // buffer, try to fill that up first - auto const avail = streambuf.capacity() - streambuf.size(); - if (avail > 0) - return (std::min)(avail, max_size); - // Try to have just one new block allocated - constexpr std::size_t low = 512; - if (streambuf.alloc_size_ > low) - return (std::min)(max_size, streambuf.alloc_size_); - // ...but enforce a 512 byte minimum. - return (std::min)(max_size, low); -} - -template -basic_streambuf& -operator<<(basic_streambuf& streambuf, T const& t) -{ - detail::write_dynabuf(streambuf, t); - return streambuf; -} - } // beast #endif diff --git a/include/beast/core/impl/read_size.ipp b/include/beast/core/impl/read_size.ipp new file mode 100644 index 0000000000..56bc81c758 --- /dev/null +++ b/include/beast/core/impl/read_size.ipp @@ -0,0 +1,75 @@ +// +// 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_IMPL_READ_SIZE_IPP +#define BEAST_IMPL_READ_SIZE_IPP + +namespace beast { + +namespace detail { + +template +struct has_read_size_helper : std::false_type {}; + +template +struct has_read_size_helper(), 512), + (void)0)> : std::true_type +{ +}; + +template +std::size_t +read_size(DynamicBuffer& buffer, + std::size_t max_size, std::true_type) +{ + return read_size_helper(buffer, max_size); +} + +template +std::size_t +read_size(DynamicBuffer& buffer, + std::size_t max_size, std::false_type) +{ + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + BOOST_ASSERT(max_size >= 1); + auto const size = buffer.size(); + auto const limit = buffer.max_size() - size; + BOOST_ASSERT(size <= buffer.max_size()); + return std::min( + std::max(512, buffer.capacity() - size), + std::min(max_size, limit)); +} + +} // detail + +template +inline +std::size_t +read_size( + DynamicBuffer& buffer, std::size_t max_size) +{ + return detail::read_size(buffer, max_size, + detail::has_read_size_helper{}); +} + +template +std::size_t +read_size_or_throw( + DynamicBuffer& buffer, std::size_t max_size) +{ + auto const n = read_size(buffer, max_size); + if(n == 0) + BOOST_THROW_EXCEPTION(std::length_error{ + "buffer overflow"}); + return n; +} + +} // beast + +#endif diff --git a/include/beast/core/impl/static_buffer.ipp b/include/beast/core/impl/static_buffer.ipp new file mode 100644 index 0000000000..13652ca56f --- /dev/null +++ b/include/beast/core/impl/static_buffer.ipp @@ -0,0 +1,129 @@ +// +// 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_IMPL_STATIC_BUFFER_IPP +#define BEAST_IMPL_STATIC_BUFFER_IPP + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { + +/* Memory is laid out thusly: + + begin_ ..|.. in_ ..|.. out_ ..|.. last_ ..|.. end_ +*/ + +inline +auto +static_buffer:: +data() const -> + const_buffers_type +{ + return {in_, dist(in_, out_)}; +} + +inline +auto +static_buffer:: +prepare(std::size_t n) -> + mutable_buffers_type +{ + return prepare_impl(n); +} + +inline +void +static_buffer:: +reset(void* p, std::size_t n) +{ + reset_impl(p, n); +} + +template +void +static_buffer:: +reset_impl(void* p, std::size_t n) +{ + begin_ = + reinterpret_cast(p); + in_ = begin_; + out_ = begin_; + last_ = begin_; + end_ = begin_ + n; +} + +template +auto +static_buffer:: +prepare_impl(std::size_t n) -> + mutable_buffers_type +{ + if(n <= dist(out_, end_)) + { + last_ = out_ + n; + return {out_, n}; + } + auto const len = size(); + if(n > capacity() - len) + BOOST_THROW_EXCEPTION(std::length_error{ + "buffer overflow"}); + if(len > 0) + std::memmove(begin_, in_, len); + in_ = begin_; + out_ = in_ + len; + last_ = out_ + n; + return {out_, n}; +} + +template +void +static_buffer:: +consume_impl(std::size_t n) +{ + if(n >= size()) + { + in_ = begin_; + out_ = in_; + return; + } + in_ += n; +} + +//------------------------------------------------------------------------------ + +template +static_buffer_n:: +static_buffer_n(static_buffer_n const& other) + : static_buffer(buf_, N) +{ + using boost::asio::buffer_copy; + this->commit(buffer_copy( + this->prepare(other.size()), other.data())); +} + +template +auto +static_buffer_n:: +operator=(static_buffer_n const& other) -> + static_buffer_n& +{ + using boost::asio::buffer_copy; + this->consume(this->size()); + this->commit(buffer_copy( + this->prepare(other.size()), other.data())); + return *this; +} + +} // beast + +#endif diff --git a/include/beast/core/impl/static_streambuf.ipp b/include/beast/core/impl/static_streambuf.ipp deleted file mode 100644 index 90a4834625..0000000000 --- a/include/beast/core/impl/static_streambuf.ipp +++ /dev/null @@ -1,307 +0,0 @@ -// -// 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_IMPL_STATIC_STREAMBUF_IPP -#define BEAST_IMPL_STATIC_STREAMBUF_IPP - -#include -#include -#include -#include -#include -#include - -namespace beast { - -class static_streambuf::const_buffers_type -{ - std::size_t n_; - std::uint8_t const* p_; - -public: - using value_type = boost::asio::const_buffer; - - class const_iterator; - - const_buffers_type() = delete; - const_buffers_type( - const_buffers_type const&) = default; - const_buffers_type& operator=( - const_buffers_type const&) = default; - - const_iterator - begin() const; - - const_iterator - end() const; - -private: - friend class static_streambuf; - - const_buffers_type( - std::uint8_t const* p, std::size_t n) - : n_(n) - , p_(p) - { - } -}; - -class static_streambuf::const_buffers_type::const_iterator -{ - std::size_t n_ = 0; - std::uint8_t const* p_ = nullptr; - -public: - using value_type = boost::asio::const_buffer; - using pointer = value_type const*; - using reference = value_type; - using difference_type = std::ptrdiff_t; - using iterator_category = - std::bidirectional_iterator_tag; - - const_iterator() = default; - const_iterator(const_iterator&& other) = default; - const_iterator(const_iterator const& other) = default; - const_iterator& operator=(const_iterator&& other) = default; - const_iterator& operator=(const_iterator const& other) = default; - - bool - operator==(const_iterator const& other) const - { - return p_ == other.p_; - } - - bool - operator!=(const_iterator const& other) const - { - return !(*this == other); - } - - reference - operator*() const - { - return value_type{p_, n_}; - } - - pointer - operator->() const = delete; - - const_iterator& - operator++() - { - p_ += n_; - return *this; - } - - const_iterator - operator++(int) - { - auto temp = *this; - ++(*this); - return temp; - } - - const_iterator& - operator--() - { - p_ -= n_; - return *this; - } - - const_iterator - operator--(int) - { - auto temp = *this; - --(*this); - return temp; - } - -private: - friend class const_buffers_type; - - const_iterator( - std::uint8_t const* p, std::size_t n) - : n_(n) - , p_(p) - { - } -}; - -inline -auto -static_streambuf::const_buffers_type::begin() const -> - const_iterator -{ - return const_iterator{p_, n_}; -} - -inline -auto -static_streambuf::const_buffers_type::end() const -> - const_iterator -{ - return const_iterator{p_ + n_, n_}; -} - -//------------------------------------------------------------------------------ - -class static_streambuf::mutable_buffers_type -{ - std::size_t n_; - std::uint8_t* p_; - -public: - using value_type = boost::asio::mutable_buffer; - - class const_iterator; - - mutable_buffers_type() = delete; - mutable_buffers_type( - mutable_buffers_type const&) = default; - mutable_buffers_type& operator=( - mutable_buffers_type const&) = default; - - const_iterator - begin() const; - - const_iterator - end() const; - -private: - friend class static_streambuf; - - mutable_buffers_type( - std::uint8_t* p, std::size_t n) - : n_(n) - , p_(p) - { - } -}; - -class static_streambuf::mutable_buffers_type::const_iterator -{ - std::size_t n_ = 0; - std::uint8_t* p_ = nullptr; - -public: - using value_type = boost::asio::mutable_buffer; - using pointer = value_type const*; - using reference = value_type; - using difference_type = std::ptrdiff_t; - using iterator_category = - std::bidirectional_iterator_tag; - - const_iterator() = default; - const_iterator(const_iterator&& other) = default; - const_iterator(const_iterator const& other) = default; - const_iterator& operator=(const_iterator&& other) = default; - const_iterator& operator=(const_iterator const& other) = default; - - bool - operator==(const_iterator const& other) const - { - return p_ == other.p_; - } - - bool - operator!=(const_iterator const& other) const - { - return !(*this == other); - } - - reference - operator*() const - { - return value_type{p_, n_}; - } - - pointer - operator->() const = delete; - - const_iterator& - operator++() - { - p_ += n_; - return *this; - } - - const_iterator - operator++(int) - { - auto temp = *this; - ++(*this); - return temp; - } - - const_iterator& - operator--() - { - p_ -= n_; - return *this; - } - - const_iterator - operator--(int) - { - auto temp = *this; - --(*this); - return temp; - } - -private: - friend class mutable_buffers_type; - - const_iterator(std::uint8_t* p, std::size_t n) - : n_(n) - , p_(p) - { - } -}; - -inline -auto -static_streambuf::mutable_buffers_type::begin() const -> - const_iterator -{ - return const_iterator{p_, n_}; -} - -inline -auto -static_streambuf::mutable_buffers_type::end() const -> - const_iterator -{ - return const_iterator{p_ + n_, n_}; -} - -//------------------------------------------------------------------------------ - - -inline -auto -static_streambuf::data() const -> - const_buffers_type -{ - return const_buffers_type{in_, - static_cast(out_ - in_)}; -} - -inline -auto -static_streambuf::prepare(std::size_t n) -> - mutable_buffers_type -{ - if(n > static_cast(end_ - out_)) - throw detail::make_exception( - "no space in streambuf", __FILE__, __LINE__); - last_ = out_ + n; - return mutable_buffers_type{out_, n}; -} - -} // beast - -#endif diff --git a/include/beast/core/impl/static_string.ipp b/include/beast/core/impl/static_string.ipp new file mode 100644 index 0000000000..6bab53b5d2 --- /dev/null +++ b/include/beast/core/impl/static_string.ipp @@ -0,0 +1,614 @@ +// +// 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_IMPL_STATIC_STRING_IPP +#define BEAST_IMPL_STATIC_STRING_IPP + +#include +#include +#include + +namespace beast { + +// +// (constructor) +// + +template +static_string:: +static_string() +{ + n_ = 0; + term(); +} + +template +static_string:: +static_string(size_type count, CharT ch) +{ + assign(count, ch); +} + +template +template +static_string:: +static_string(static_string const& other, + size_type pos) +{ + assign(other, pos); +} + +template +template +static_string:: +static_string(static_string const& other, + size_type pos, size_type count) +{ + assign(other, pos, count); +} + +template +static_string:: +static_string(CharT const* s, size_type count) +{ + assign(s, count); +} + +template +static_string:: +static_string(CharT const* s) +{ + assign(s); +} + +template +template +static_string:: +static_string(InputIt first, InputIt last) +{ + assign(first, last); +} + +template +static_string:: +static_string(static_string const& s) +{ + assign(s); +} + +template +template +static_string:: +static_string(static_string const& s) +{ + assign(s); +} + +template +static_string:: +static_string(std::initializer_list init) +{ + assign(init.begin(), init.end()); +} + +template +static_string:: +static_string(string_view_type sv) +{ + assign(sv); +} + +template +template +static_string:: +static_string(T const& t, size_type pos, size_type n) +{ + assign(t, pos, n); +} + +// +// (assignment) +// + +template +auto +static_string:: +assign(size_type count, CharT ch) -> + static_string& +{ + if(count > max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "count > max_size()"}); + n_ = count; + Traits::assign(&s_[0], n_, ch); + term(); + return *this; +} + +template +auto +static_string:: +assign(static_string const& str) -> + static_string& +{ + n_ = str.n_; + Traits::copy(&s_[0], &str.s_[0], n_ + 1); + return *this; +} + +template +template +auto +static_string:: +assign(static_string const& str, + size_type pos, size_type count) -> + static_string& +{ + auto const ss = str.substr(pos, count); + return assign(ss.data(), ss.size()); +} + +template +auto +static_string:: +assign(CharT const* s, size_type count) -> + static_string& +{ + if(count > max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "count > max_size()"}); + n_ = count; + Traits::copy(&s_[0], s, n_); + term(); + return *this; +} + +template +template +auto +static_string:: +assign(InputIt first, InputIt last) -> + static_string& +{ + std::size_t const n = std::distance(first, last); + if(n > max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "n > max_size()"}); + n_ = n; + for(auto it = &s_[0]; first != last; ++it, ++first) + Traits::assign(*it, *first); + term(); + return *this; +} + +template +template +auto +static_string:: +assign(T const& t, size_type pos, size_type count) -> + typename std::enable_if::value, static_string&>::type +{ + auto const sv = string_view_type(t).substr(pos, count); + if(sv.size() > max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "sv.size() > max_size()"}); + n_ = sv.size(); + Traits::copy(&s_[0], &sv[0], n_); + term(); + return *this; +} + +// +// Element access +// + +template +auto +static_string:: +at(size_type pos) -> + reference +{ + if(pos >= size()) + BOOST_THROW_EXCEPTION(std::out_of_range{ + "pos >= size()"}); + return s_[pos]; +} + +template +auto +static_string:: +at(size_type pos) const -> + const_reference +{ + if(pos >= size()) + BOOST_THROW_EXCEPTION(std::out_of_range{ + "pos >= size()"}); + return s_[pos]; +} + +// +// Capacity +// + +template +void +static_string:: +reserve(std::size_t n) +{ + if(n > max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "n > max_size()"}); +} + +// +// Operations +// + +template +void +static_string:: +clear() +{ + n_ = 0; + term(); +} + +template +auto +static_string:: +insert(size_type index, size_type count, CharT ch) -> + static_string& +{ + if(index > size()) + BOOST_THROW_EXCEPTION(std::out_of_range{ + "index > size()"}); + insert(begin() + index, count, ch); + return *this; +} + +template +auto +static_string:: +insert(size_type index, CharT const* s, size_type count) -> + static_string& +{ + if(index > size()) + BOOST_THROW_EXCEPTION(std::out_of_range{ + "index > size()"}); + if(size() + count > max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "size() + count > max_size()"}); + Traits::move( + &s_[index + count], &s_[index], size() - index); + n_ += count; + Traits::copy(&s_[index], s, count); + term(); + return *this; +} + +template +template +auto +static_string:: +insert(size_type index, + static_string const& str, + size_type index_str, size_type count) -> + static_string& +{ + auto const ss = str.substr(index_str, count); + return insert(index, ss.data(), ss.size()); +} + +template +auto +static_string:: +insert(const_iterator pos, size_type count, CharT ch) -> + iterator +{ + if(size() + count > max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "size() + count() > max_size()"}); + auto const index = pos - &s_[0]; + Traits::move( + &s_[index + count], &s_[index], size() - index); + n_ += count; + Traits::assign(&s_[index], count, ch); + term(); + return &s_[index]; +} + +template +template +auto +static_string:: +insert(const_iterator pos, InputIt first, InputIt last) -> + typename std::enable_if< + detail::is_input_iterator::value, + iterator>::type +{ + std::size_t const count = std::distance(first, last); + if(size() + count > max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "size() + count > max_size()"}); + std::size_t const index = pos - begin(); + Traits::move( + &s_[index + count], &s_[index], size() - index); + n_ += count; + for(auto it = begin() + index; + first != last; ++it, ++first) + Traits::assign(*it, *first); + term(); + return begin() + index; +} + +template +template +auto +static_string:: +insert(size_type index, const T& t, + size_type index_str, size_type count) -> + typename std::enable_if::value && + ! std::is_convertible::value, + static_string&>::type +{ + auto const str = + string_view_type(t).substr(index_str, count); + return insert(index, str.data(), str.size()); +} + +template +auto +static_string:: +erase(size_type index, size_type count) -> + static_string& +{ + if(index > size()) + BOOST_THROW_EXCEPTION(std::out_of_range{ + "index > size()"}); + auto const n = (std::min)(count, size() - index); + Traits::move( + &s_[index], &s_[index + n], size() - (index + n) + 1); + n_ -= n; + return *this; +} + +template +auto +static_string:: +erase(const_iterator pos) -> + iterator +{ + erase(pos - begin(), 1); + return begin() + (pos - begin()); +} + +template +auto +static_string:: +erase(const_iterator first, const_iterator last) -> + iterator +{ + erase(first - begin(), + std::distance(first, last)); + return begin() + (first - begin()); +} + +template +void +static_string:: +push_back(CharT ch) +{ + if(size() >= max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "size() >= max_size()"}); + Traits::assign(s_[n_++], ch); + term(); +} + +template +template +auto +static_string:: +append(static_string const& str, + size_type pos, size_type count) -> + static_string& +{ + // Valid range is [0, size) + if(pos >= str.size()) + BOOST_THROW_EXCEPTION(std::out_of_range{ + "pos > str.size()"}); + string_view_type const ss{&str.s_[pos], + (std::min)(count, str.size() - pos)}; + insert(size(), ss.data(), ss.size()); + return *this; +} + +template +auto +static_string:: +substr(size_type pos, size_type count) const -> + string_view_type +{ + if(pos > size()) + BOOST_THROW_EXCEPTION(std::out_of_range{ + "pos > size()"}); + return{&s_[pos], (std::min)(count, size() - pos)}; +} + +template +auto +static_string:: +copy(CharT* dest, size_type count, size_type pos) const -> + size_type +{ + auto const str = substr(pos, count); + Traits::copy(dest, str.data(), str.size()); + return str.size(); +} + +template +void +static_string:: +resize(std::size_t n) +{ + if(n > max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "n > max_size()"}); + n_ = n; + term(); +} + +template +void +static_string:: +resize(std::size_t n, CharT c) +{ + if(n > max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "n > max_size()"}); + if(n > n_) + Traits::assign(&s_[n_], n - n_, c); + n_ = n; + term(); +} + +template +void +static_string:: +swap(static_string& str) +{ + static_string tmp(str); + str.n_ = n_; + Traits::copy(&str.s_[0], &s_[0], n_ + 1); + n_ = tmp.n_; + Traits::copy(&s_[0], &tmp.s_[0], n_ + 1); +} + +template +template +void +static_string:: +swap(static_string& str) +{ + if(size() > str.max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "size() > str.max_size()"}); + if(str.size() > max_size()) + BOOST_THROW_EXCEPTION(std::length_error{ + "str.size() > max_size()"}); + static_string tmp(str); + str.n_ = n_; + Traits::copy(&str.s_[0], &s_[0], n_ + 1); + n_ = tmp.n_; + Traits::copy(&s_[0], &tmp.s_[0], n_ + 1); +} + + +template +auto +static_string:: +assign_char(CharT ch, std::true_type) -> + static_string& +{ + n_ = 1; + Traits::assign(s_[0], ch); + term(); + return *this; +} + +template +auto +static_string:: +assign_char(CharT, std::false_type) -> + static_string& +{ + BOOST_THROW_EXCEPTION(std::length_error{ + "max_size() == 0"}); +} + +namespace detail { + +template +static_string +to_static_string(Integer x, std::true_type) +{ + if(x == 0) + return {'0'}; + static_string s; + if(x < 0) + { + x = -x; + char buf[max_digits(sizeof(x))]; + char* p = buf; + for(;x > 0; x /= 10) + *p++ = "0123456789"[x % 10]; + s.resize(1 + p - buf); + s[0] = '-'; + auto d = &s[1]; + while(p > buf) + *d++ = *--p; + } + else + { + char buf[max_digits(sizeof(x))]; + char* p = buf; + for(;x > 0; x /= 10) + *p++ = "0123456789"[x % 10]; + s.resize(p - buf); + auto d = &s[0]; + while(p > buf) + *d++ = *--p; + } + return s; +} + +template +static_string +to_static_string(Integer x, std::false_type) +{ + if(x == 0) + return {'0'}; + char buf[max_digits(sizeof(x))]; + char* p = buf; + for(;x > 0; x /= 10) + *p++ = "0123456789"[x % 10]; + static_string s; + s.resize(p - buf); + auto d = &s[0]; + while(p > buf) + *d++ = *--p; + return s; +} + +} // detail + +template +static_string +to_static_string(Integer x) +{ + using CharT = char; + using Traits = std::char_traits; + BOOST_STATIC_ASSERT(std::is_integral::value); + char buf[detail::max_digits(sizeof(Integer))]; + auto last = buf + sizeof(buf); + auto it = detail::raw_to_string< + CharT, Integer, Traits>(last, sizeof(buf), x); + static_string s; + s.resize(static_cast(last - it)); + auto p = s.data(); + while(it < last) + Traits::assign(*p++, *it++); + return s; +} + +} // beast + +#endif diff --git a/include/beast/core/impl/string_param.ipp b/include/beast/core/impl/string_param.ipp new file mode 100644 index 0000000000..8bcccca4c6 --- /dev/null +++ b/include/beast/core/impl/string_param.ipp @@ -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) +// + +#ifndef BEAST_IMPL_STRING_PARAM_IPP +#define BEAST_IMPL_STRING_PARAM_IPP + +namespace beast { + +template +typename std::enable_if< + std::is_integral::value>::type +string_param:: +print(T const& t) +{ + auto const last = buf_ + sizeof(buf_); + auto const it = detail::raw_to_string< + char, T, std::char_traits>( + last, sizeof(buf_), t); + sv_ = {it, static_cast( + last - it)}; +} + +template +typename std::enable_if< + ! std::is_integral::value && + ! std::is_convertible::value +>::type +string_param:: +print(T const& t) +{ + os_.emplace(buf_, sizeof(buf_)); + *os_ << t; + os_->flush(); + sv_ = os_->str(); +} + +inline +void +string_param:: +print(string_view const& sv) +{ + sv_ = sv; +} + +template +typename std::enable_if< + std::is_integral::value>::type +string_param:: +print_1(T const& t) +{ + char buf[detail::max_digits(sizeof(T))]; + auto const last = buf + sizeof(buf); + auto const it = detail::raw_to_string< + char, T, std::char_traits>( + last, sizeof(buf), t); + *os_ << string_view{it, + static_cast(last - it)}; +} + +template +typename std::enable_if< + ! std::is_integral::value>::type +string_param:: +print_1(T const& t) +{ + *os_ << t; +} + +template +void +string_param:: +print_n(T0 const& t0, TN const&... tn) +{ + print_1(t0); + print_n(tn...); +} + +template +void +string_param:: +print(T0 const& t0, T1 const& t1, TN const&... tn) +{ + os_.emplace(buf_, sizeof(buf_)); + print_1(t0); + print_1(t1); + print_n(tn...); + os_->flush(); + sv_ = os_->str(); +} + +template +string_param:: +string_param(Args const&... args) +{ + print(args...); +} + +} // beast + +#endif diff --git a/include/beast/core/multi_buffer.hpp b/include/beast/core/multi_buffer.hpp new file mode 100644 index 0000000000..4b961756fc --- /dev/null +++ b/include/beast/core/multi_buffer.hpp @@ -0,0 +1,323 @@ +// +// 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_MULTI_BUFFER_HPP +#define BEAST_MULTI_BUFFER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { + +/** A @b DynamicBuffer that uses multiple buffers internally. + + The implementation uses a sequence of one or more character arrays + of varying sizes. Additional character array objects are appended to + the sequence to accommodate changes in the size of the character + sequence. + + @note Meets the requirements of @b DynamicBuffer. + + @tparam Allocator The allocator to use for managing memory. +*/ +template +class basic_multi_buffer +#if ! BEAST_DOXYGEN + : private detail::empty_base_optimization< + typename std::allocator_traits:: + template rebind_alloc> +#endif +{ +public: +#if BEAST_DOXYGEN + /// The type of allocator used. + using allocator_type = Allocator; +#else + using allocator_type = typename + std::allocator_traits:: + template rebind_alloc; +#endif + +private: + // Storage for the list of buffers representing the input + // and output sequences. The allocation for each element + // contains `element` followed by raw storage bytes. + class element; + + using alloc_traits = std::allocator_traits; + using list_type = typename boost::intrusive::make_list>::type; + using iterator = typename list_type::iterator; + using const_iterator = typename list_type::const_iterator; + + using size_type = typename std::allocator_traits::size_type; + using const_buffer = boost::asio::const_buffer; + using mutable_buffer = boost::asio::mutable_buffer; + + static_assert(std::is_base_of::iterator_category>::value, + "BidirectionalIterator requirements not met"); + + static_assert(std::is_base_of::iterator_category>::value, + "BidirectionalIterator requirements not met"); + + std::size_t max_ = + (std::numeric_limits::max)(); + list_type list_; // list of allocated buffers + iterator out_; // element that contains out_pos_ + size_type in_size_ = 0; // size of the input sequence + size_type in_pos_ = 0; // input offset in list_.front() + size_type out_pos_ = 0; // output offset in *out_ + size_type out_end_ = 0; // output end offset in list_.back() + +public: +#if BEAST_DOXYGEN + /// The type used to represent the input sequence as a list of buffers. + using const_buffers_type = implementation_defined; + + /// The type used to represent the output sequence as a list of buffers. + using mutable_buffers_type = implementation_defined; + +#else + class const_buffers_type; + + class mutable_buffers_type; + +#endif + + /// Destructor + ~basic_multi_buffer(); + + /** Constructor + + Upon construction, capacity will be zero. + */ + basic_multi_buffer(); + + /** Constructor. + + @param limit The setting for @ref max_size. + */ + explicit + basic_multi_buffer(std::size_t limit); + + /** Constructor. + + @param alloc The allocator to use. + */ + basic_multi_buffer(Allocator const& alloc); + + /** Constructor. + + @param limit The setting for @ref max_size. + + @param alloc The allocator to use. + */ + basic_multi_buffer( + std::size_t limit, Allocator const& alloc); + + /** Move constructor + + After the move, `*this` will have an empty output sequence. + + @param other The object to move from. After the move, + The object's state will be as if constructed using + its current allocator and limit. + */ + basic_multi_buffer(basic_multi_buffer&& other); + + /** Move constructor + + After the move, `*this` will have an empty output sequence. + + @param other The object to move from. After the move, + The object's state will be as if constructed using + its current allocator and limit. + + @param alloc The allocator to use. + */ + basic_multi_buffer(basic_multi_buffer&& other, + Allocator const& alloc); + + /** Copy constructor. + + @param other The object to copy from. + */ + basic_multi_buffer(basic_multi_buffer const& other); + + /** Copy constructor + + @param other The object to copy from. + + @param alloc The allocator to use. + */ + basic_multi_buffer(basic_multi_buffer const& other, + Allocator const& alloc); + + /** Copy constructor. + + @param other The object to copy from. + */ + template + basic_multi_buffer(basic_multi_buffer< + OtherAlloc> const& other); + + /** Copy constructor. + + @param other The object to copy from. + + @param alloc The allocator to use. + */ + template + basic_multi_buffer(basic_multi_buffer< + OtherAlloc> const& other, allocator_type const& alloc); + + /** Move assignment + + After the move, `*this` will have an empty output sequence. + + @param other The object to move from. After the move, + The object's state will be as if constructed using + its current allocator and limit. + */ + basic_multi_buffer& + operator=(basic_multi_buffer&& other); + + /** Copy assignment + + After the copy, `*this` will have an empty output sequence. + + @param other The object to copy from. + */ + basic_multi_buffer& operator=(basic_multi_buffer const& other); + + /** Copy assignment + + After the copy, `*this` will have an empty output sequence. + + @param other The object to copy from. + */ + template + basic_multi_buffer& operator=( + basic_multi_buffer const& other); + + /// Returns a copy of the associated allocator. + allocator_type + get_allocator() const + { + return this->member(); + } + + /// Returns the size of the input sequence. + size_type + size() const + { + return in_size_; + } + + /// Returns the permitted maximum sum of the sizes of the input and output sequence. + size_type + max_size() const + { + return max_; + } + + /// Returns the maximum sum of the sizes of the input sequence and output sequence the buffer can hold without requiring reallocation. + std::size_t + capacity() const; + + /** Get a list of buffers that represents the input sequence. + + @note These buffers remain valid across subsequent calls to `prepare`. + */ + const_buffers_type + data() const; + + /** Get a list of buffers that represents the output sequence, with the given size. + + @note Buffers representing the input sequence acquired prior to + this call remain valid. + */ + mutable_buffers_type + prepare(size_type n); + + /** Move bytes from the output sequence to the input sequence. + + @note Buffers representing the input sequence acquired prior to + this call remain valid. + */ + void + commit(size_type n); + + /// Remove bytes from the input sequence. + void + consume(size_type n); + + template + friend + void + swap( + basic_multi_buffer& lhs, + basic_multi_buffer& rhs); + +private: + template + friend class basic_multi_buffer; + + void + delete_element(element& e); + + void + delete_list(); + + void + reset(); + + template + void + copy_from(DynamicBuffer const& other); + + void + move_assign(basic_multi_buffer& other, std::false_type); + + void + move_assign(basic_multi_buffer& other, std::true_type); + + void + copy_assign(basic_multi_buffer const& other, std::false_type); + + void + copy_assign(basic_multi_buffer const& other, std::true_type); + + void + swap(basic_multi_buffer&); + + void + swap(basic_multi_buffer&, std::true_type); + + void + swap(basic_multi_buffer&, std::false_type); + + void + debug_check() const; +}; + +/// A typical multi buffer +using multi_buffer = basic_multi_buffer>; + +} // beast + +#include + +#endif diff --git a/include/beast/core/ostream.hpp b/include/beast/core/ostream.hpp new file mode 100644 index 0000000000..49cbd69aae --- /dev/null +++ b/include/beast/core/ostream.hpp @@ -0,0 +1,99 @@ +// +// 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_WRITE_OSTREAM_HPP +#define BEAST_WRITE_OSTREAM_HPP + +#include +#include +#include +#include +#include +#include + +namespace beast { + +/** Return an object representing a @b ConstBufferSequence. + + This function wraps a reference to a buffer sequence and permits + the following operation: + + @li `operator<<` to `std::ostream`. No character translation is + performed; unprintable and null characters will be transferred + as-is to the output stream. + + @par Example + @code + multi_buffer b; + ... + std::cout << buffers(b.data()) << std::endl; + @endcode + + @param b An object meeting the requirements of @b ConstBufferSequence + to be streamed. The implementation will make a copy of this object. + Ownership of the underlying memory is not transferred, the application + is still responsible for managing its lifetime. +*/ +template +#if BEAST_DOXYGEN +implementation_defined +#else +detail::buffers_helper +#endif +buffers(ConstBufferSequence const& b) +{ + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + return detail::buffers_helper< + ConstBufferSequence>{b}; +} + +/** Return an output stream that formats values into a @b DynamicBuffer. + + This function wraps the caller provided @b DynamicBuffer into + a `std::ostream` derived class, to allow `operator<<` stream style + formatting operations. + + @par Example + @code + ostream(buffer) << "Hello, world!" << std::endl; + @endcode + + @note Calling members of the underlying buffer before the output + stream is destroyed results in undefined behavior. + + @param buffer An object meeting the requirements of @b DynamicBuffer + into which the formatted output will be placed. + + @return An object derived from `std::ostream` which redirects output + The wrapped dynamic buffer is not modified, a copy is made instead. + Ownership of the underlying memory is not transferred, the application + is still responsible for managing its lifetime. The caller is + responsible for ensuring the dynamic buffer is not destroyed for the + lifetime of the output stream. +*/ +template +#if BEAST_DOXYGEN +implementation_defined +#else +detail::ostream_helper< + DynamicBuffer, char, std::char_traits, + detail::basic_streambuf_movable::value> +#endif +ostream(DynamicBuffer& buffer) +{ + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + return detail::ostream_helper< + DynamicBuffer, char, std::char_traits, + detail::basic_streambuf_movable::value>{buffer}; +} + +} // beast + +#endif diff --git a/include/beast/core/placeholders.hpp b/include/beast/core/placeholders.hpp deleted file mode 100644 index c112778936..0000000000 --- a/include/beast/core/placeholders.hpp +++ /dev/null @@ -1,30 +0,0 @@ -// -// 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_PLACEHOLDERS_HPP -#define BEAST_PLACEHOLDERS_HPP - -#include -#include - -namespace beast { -namespace asio { - -namespace placeholders { -// asio placeholders that work with std::bind -namespace { -static auto const error (std::placeholders::_1); -static auto const bytes_transferred (std::placeholders::_2); -static auto const iterator (std::placeholders::_2); -static auto const signal_number (std::placeholders::_2); -} -} // placeholders - -} // asio -} // beast - -#endif diff --git a/include/beast/core/prepare_buffer.hpp b/include/beast/core/prepare_buffer.hpp deleted file mode 100644 index ab0fae645b..0000000000 --- a/include/beast/core/prepare_buffer.hpp +++ /dev/null @@ -1,69 +0,0 @@ -// -// 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_PREPARE_BUFFER_HPP -#define BEAST_PREPARE_BUFFER_HPP - -#include -#include -#include - -namespace beast { - -/** Return a shortened buffer. - - The returned buffer points to the same memory as the - passed buffer, but with a size that is equal to or less - than the size of the original buffer. - - @param n The size of the returned buffer. - - @param buffer The buffer to shorten. Ownership of the - underlying memory is not transferred. - - @return A new buffer that points to the first `n` bytes - of the original buffer. -*/ -inline -boost::asio::const_buffer -prepare_buffer(std::size_t n, - boost::asio::const_buffer buffer) -{ - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - return { buffer_cast(buffer), - (std::min)(n, buffer_size(buffer)) }; -} - -/** Return a shortened buffer. - - The returned buffer points to the same memory as the - passed buffer, but with a size that is equal to or less - than the size of the original buffer. - - @param n The size of the returned buffer. - - @param buffer The buffer to shorten. Ownership of the - underlying memory is not transferred. - - @return A new buffer that points to the first `n` bytes - of the original buffer. -*/ -inline -boost::asio::mutable_buffer -prepare_buffer(std::size_t n, - boost::asio::mutable_buffer buffer) -{ - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - return { buffer_cast(buffer), - (std::min)(n, buffer_size(buffer)) }; -} - -} // beast - -#endif diff --git a/include/beast/core/prepare_buffers.hpp b/include/beast/core/prepare_buffers.hpp deleted file mode 100644 index 7db31b5b80..0000000000 --- a/include/beast/core/prepare_buffers.hpp +++ /dev/null @@ -1,53 +0,0 @@ -// -// 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_PREPARE_BUFFERS_HPP -#define BEAST_PREPARE_BUFFERS_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { - -/** Return a shortened buffer sequence. - - This function returns a new buffer sequence which adapts the - passed buffer sequence and efficiently presents a shorter subset - of the original list of buffers starting with the first byte of - the original sequence. - - @param n The maximum number of bytes in the wrapped - sequence. If this is larger than the size of passed, - buffers, the resulting sequence will represent the - entire input sequence. - - @param buffers The buffer sequence to adapt. A copy of - the sequence will be made, but ownership of the underlying - memory is not transferred. -*/ -template -#if GENERATING_DOCS -implementation_defined -#else -inline -detail::prepared_buffers -#endif -prepare_buffers(std::size_t n, BufferSequence const& buffers) -{ - return detail::prepared_buffers(n, buffers); -} - -} // beast - -#endif diff --git a/include/beast/core/read_size.hpp b/include/beast/core/read_size.hpp new file mode 100644 index 0000000000..326877f4a2 --- /dev/null +++ b/include/beast/core/read_size.hpp @@ -0,0 +1,60 @@ +// +// 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_READ_SIZE_HELPER_HPP +#define BEAST_READ_SIZE_HELPER_HPP + +#include +#include +#include + +namespace beast { + +/** Returns a natural read size. + + This function inspects the capacity, size, and maximum + size of the dynamic buffer. Then it computes a natural + read size given the passed-in upper limit. It favors + a read size that does not require a reallocation, subject + to a reasonable minimum to avoid tiny reads. + + @param buffer The dynamic buffer to inspect. + + @param max_size An upper limit on the returned value. + + @note If the buffer is already at its maximum size, zero + is returned. +*/ +template +std::size_t +read_size(DynamicBuffer& buffer, std::size_t max_size); + +/** Returns a natural read size or throw if the buffer is full. + + This function inspects the capacity, size, and maximum + size of the dynamic buffer. Then it computes a natural + read size given the passed-in upper limit. It favors + a read size that does not require a reallocation, subject + to a reasonable minimum to avoid tiny reads. + + @param buffer The dynamic buffer to inspect. + + @param max_size An upper limit on the returned value. + + @throws std::length_error if `max_size > 0` and the buffer + is full. +*/ +template +std::size_t +read_size_or_throw(DynamicBuffer& buffer, + std::size_t max_size); + +} // beast + +#include + +#endif diff --git a/include/beast/core/span.hpp b/include/beast/core/span.hpp new file mode 100644 index 0000000000..1d465ce42f --- /dev/null +++ b/include/beast/core/span.hpp @@ -0,0 +1,211 @@ +// +// 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_CORE_SPAN_HPP +#define BEAST_CORE_SPAN_HPP + +#include +#include +#include +#include +#include +#include + +namespace beast { + +/** A range of bytes expressed as a ContiguousContainer + + This class implements a non-owning reference to a storage + area of a certain size and having an underlying integral + type with size of 1. + + @tparam T The type pointed to by span iterators +*/ +template +class span +{ + T* data_ = nullptr; + std::size_t size_ = 0; + +public: + /// The type of value, including cv qualifiers + using element_type = T; + + /// The type of value of each span element + using value_type = typename std::remove_const::type; + + /// The type of integer used to index the span + using index_type = std::ptrdiff_t; + + /// A pointer to a span element + using pointer = T*; + + /// A reference to a span element + using reference = T&; + + /// The iterator used by the container + using iterator = pointer; + + /// The const pointer used by the container + using const_pointer = T const*; + + /// The const reference used by the container + using const_reference = T const&; + + /// The const iterator used by the container + using const_iterator = const_pointer; + + /// Constructor + span() = default; + + /// Constructor + span(span const&) = default; + + /// Assignment + span& operator=(span const&) = default; + + /** Constructor + + @param data A pointer to the beginning of the range of elements + + @param size The number of elements pointed to by `data` + */ + span(T* data, std::size_t size) + : data_(data), size_(size) + { + } + + /** Constructor + + @param container The container to construct from + */ + template::value>::type +#endif + > + explicit + span(ContiguousContainer&& container) + : data_(container.data()) + , size_(container.size()) + { + } + +#if ! BEAST_DOXYGEN + template + explicit + span(std::basic_string& s) + : data_(&s[0]) + , size_(s.size()) + { + } + + template + explicit + span(std::basic_string const& s) + : data_(s.data()) + , size_(s.size()) + { + } +#endif + + /** Assignment + + @param container The container to assign from + */ + template +#if BEAST_DOXYGEN + span& +#else + typename std::enable_if::value, + span&>::type +#endif + operator=(ContiguousContainer&& container) + { + data_ = container.data(); + size_ = container.size(); + return *this; + } + +#if ! BEAST_DOXYGEN + template + span& + operator=(std::basic_string< + CharT, Traits, Allocator>& s) + { + data_ = &s[0]; + size_ = s.size(); + return *this; + } + + template + span& + operator=(std::basic_string< + CharT, Traits, Allocator> const& s) + { + data_ = s.data(); + size_ = s.size(); + return *this; + } +#endif + + /// Returns `true` if the span is empty + bool + empty() const + { + return size_ == 0; + } + + /// Returns a pointer to the beginning of the span + T* + data() const + { + return data_; + } + + /// Returns the number of elements in the span + std::size_t + size() const + { + return size_; + } + + /// Returns an iterator to the beginning of the span + const_iterator + begin() const + { + return data_; + } + + /// Returns an iterator to the beginning of the span + const_iterator + cbegin() const + { + return data_; + } + + /// Returns an iterator to one past the end of the span + const_iterator + end() const + { + return data_ + size_; + } + + /// Returns an iterator to one past the end of the span + const_iterator + cend() const + { + return data_ + size_; + } +}; + +} // beast + +#endif diff --git a/include/beast/core/static_buffer.hpp b/include/beast/core/static_buffer.hpp new file mode 100644 index 0000000000..1f86c3364e --- /dev/null +++ b/include/beast/core/static_buffer.hpp @@ -0,0 +1,218 @@ +// +// 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_STATIC_BUFFER_HPP +#define BEAST_STATIC_BUFFER_HPP + +#include +#include +#include +#include +#include + +namespace beast { + +/** A @b DynamicBuffer with a fixed size internal buffer. + + Ownership of the underlying storage belongs to the derived class. + + @note Variables are usually declared using the template class + @ref static_buffer_n; however, to reduce the number of instantiations + of template functions receiving static stream buffer arguments in a + deduced context, the signature of the receiving function should use + @ref static_buffer. + + When used with @ref static_buffer_n this implements a dynamic + buffer using no memory allocations. + + @see @ref static_buffer_n +*/ +class static_buffer +{ + char* begin_; + char* in_; + char* out_; + char* last_; + char* end_; + + static_buffer(static_buffer const& other) = delete; + static_buffer& operator=(static_buffer const&) = delete; + +public: + /// The type used to represent the input sequence as a list of buffers. + using const_buffers_type = boost::asio::const_buffers_1; + + /// The type used to represent the output sequence as a list of buffers. + using mutable_buffers_type = boost::asio::mutable_buffers_1; + + /** Constructor. + + This creates a dynamic buffer using the provided storage area. + + @param p A pointer to valid storage of at least `n` bytes. + + @param n The number of valid bytes pointed to by `p`. + */ + static_buffer(void* p, std::size_t n) + { + reset_impl(p, n); + } + + /// Return the size of the input sequence. + std::size_t + size() const + { + return out_ - in_; + } + + /// Return the maximum sum of the input and output sequence sizes. + std::size_t + max_size() const + { + return dist(begin_, end_); + } + + /// Return the maximum sum of input and output sizes that can be held without an allocation. + std::size_t + capacity() const + { + return max_size(); + } + + /** Get a list of buffers that represent the input sequence. + + @note These buffers remain valid across subsequent calls to `prepare`. + */ + const_buffers_type + data() const; + + /** Get a list of buffers that represent the output sequence, with the given size. + + @throws std::length_error if the size would exceed the limit + imposed by the underlying mutable buffer sequence. + + @note Buffers representing the input sequence acquired prior to + this call remain valid. + */ + mutable_buffers_type + prepare(std::size_t n); + + /** Move bytes from the output sequence to the input sequence. + + @note Buffers representing the input sequence acquired prior to + this call remain valid. + */ + void + commit(std::size_t n) + { + out_ += std::min(n, last_ - out_); + } + + /// Remove bytes from the input sequence. + void + consume(std::size_t n) + { + consume_impl(n); + } + +protected: + /** Default constructor. + + The buffer will be in an undefined state. It is necessary + for the derived class to call @ref reset in order to + initialize the object. + */ + static_buffer(); + + /** Reset the pointed-to buffer. + + This function resets the internal state to the buffer provided. + All input and output sequences are invalidated. This function + allows the derived class to construct its members before + initializing the static buffer. + + @param p A pointer to valid storage of at least `n` bytes. + + @param n The number of valid bytes pointed to by `p`. + */ + void + reset(void* p, std::size_t n); + +private: + static + inline + std::size_t + dist(char const* first, char const* last) + { + return static_cast(last - first); + } + + template + void + reset_impl(void* p, std::size_t n); + + template + mutable_buffers_type + prepare_impl(std::size_t n); + + template + void + consume_impl(std::size_t n); +}; + +//------------------------------------------------------------------------------ + +/** A @b DynamicBuffer with a fixed size internal buffer. + + This implements a dynamic buffer using no memory allocations. + + @tparam N The number of bytes in the internal buffer. + + @note To reduce the number of template instantiations when passing + objects of this type in a deduced context, the signature of the + receiving function should use @ref static_buffer instead. + + @see @ref static_buffer +*/ +template +class static_buffer_n : public static_buffer +{ + char buf_[N]; + +public: + /// Copy constructor + static_buffer_n(static_buffer_n const&); + + /// Copy assignment + static_buffer_n& operator=(static_buffer_n const&); + + /// Construct a static buffer. + static_buffer_n() + : static_buffer(buf_, N) + { + } + + /// Returns the @ref static_buffer portion of this object + static_buffer& + base() + { + return *this; + } + + /// Returns the @ref static_buffer portion of this object + static_buffer const& + base() const + { + return *this; + } +}; + +} // beast + +#include + +#endif diff --git a/include/beast/core/static_streambuf.hpp b/include/beast/core/static_streambuf.hpp deleted file mode 100644 index 75ced50a37..0000000000 --- a/include/beast/core/static_streambuf.hpp +++ /dev/null @@ -1,199 +0,0 @@ -// -// 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_STATIC_STREAMBUF_HPP -#define BEAST_STATIC_STREAMBUF_HPP - -#include -#include -#include -#include -#include - -namespace beast { - -/** A @b `DynamicBuffer` with a fixed size internal buffer. - - Ownership of the underlying storage belongs to the derived class. - - @note Variables are usually declared using the template class - @ref static_streambuf_n; however, to reduce the number of instantiations - of template functions receiving static stream buffer arguments in a - deduced context, the signature of the receiving function should use - @ref static_streambuf. -*/ -class static_streambuf -{ -#if GENERATING_DOCS -private: -#else -protected: -#endif - std::uint8_t* begin_; - std::uint8_t* in_; - std::uint8_t* out_; - std::uint8_t* last_; - std::uint8_t* end_; - -public: -#if GENERATING_DOCS - /// The type used to represent the input sequence as a list of buffers. - using const_buffers_type = implementation_defined; - - /// The type used to represent the output sequence as a list of buffers. - using mutable_buffers_type = implementation_defined; - -#else - class const_buffers_type; - class mutable_buffers_type; - - static_streambuf( - static_streambuf const& other) noexcept = delete; - - static_streambuf& operator=( - static_streambuf const&) noexcept = delete; - -#endif - - /// Return the size of the input sequence. - std::size_t - size() const - { - return out_ - in_; - } - - /// Return the maximum sum of the input and output sequence sizes. - std::size_t - max_size() const - { - return end_ - begin_; - } - - /// Return the maximum sum of input and output sizes that can be held without an allocation. - std::size_t - capacity() const - { - return end_ - in_; - } - - /** Get a list of buffers that represent the input sequence. - - @note These buffers remain valid across subsequent calls to `prepare`. - */ - const_buffers_type - data() const; - - /** Get a list of buffers that represent the output sequence, with the given size. - - @throws std::length_error if the size would exceed the limit - imposed by the underlying mutable buffer sequence. - - @note Buffers representing the input sequence acquired prior to - this call remain valid. - */ - mutable_buffers_type - prepare(std::size_t n); - - /** Move bytes from the output sequence to the input sequence. - - @note Buffers representing the input sequence acquired prior to - this call remain valid. - */ - void - commit(std::size_t n) - { - out_ += std::min(n, last_ - out_); - } - - /// Remove bytes from the input sequence. - void - consume(std::size_t n) - { - in_ += std::min(n, out_ - in_); - } - -#if GENERATING_DOCS -private: -#else -protected: -#endif - static_streambuf(std::uint8_t* p, std::size_t n) - { - reset(p, n); - } - - void - reset(std::uint8_t* p, std::size_t n) - { - begin_ = p; - in_ = p; - out_ = p; - last_ = p; - end_ = p + n; - } -}; - -//------------------------------------------------------------------------------ - -/** A `DynamicBuffer` with a fixed size internal buffer. - - @tparam N The number of bytes in the internal buffer. - - @note To reduce the number of template instantiations when passing - objects of this type in a deduced context, the signature of the - receiving function should use `static_streambuf` instead. -*/ -template -class static_streambuf_n - : public static_streambuf -#if ! GENERATING_DOCS - , private boost::base_from_member< - std::array> -#endif -{ - using member_type = boost::base_from_member< - std::array>; -public: -#if GENERATING_DOCS -private: -#endif - static_streambuf_n( - static_streambuf_n const&) = delete; - static_streambuf_n& operator=( - static_streambuf_n const&) = delete; -#if GENERATING_DOCS -public: -#endif - - /// Construct a static stream buffer. - static_streambuf_n() - : static_streambuf( - member_type::member.data(), - member_type::member.size()) - { - } - - /** Reset the stream buffer. - - Postconditions: - The input sequence and output sequence are empty, - `max_size()` returns `N`. - */ - void - reset() - { - static_streambuf::reset( - member_type::member.data(), - member_type::member.size()); - } -}; - -} // beast - -#include - -#endif diff --git a/include/beast/core/static_string.hpp b/include/beast/core/static_string.hpp index 6ba5332cac..0d0dc8f201 100644 --- a/include/beast/core/static_string.hpp +++ b/include/beast/core/static_string.hpp @@ -5,20 +5,24 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_WEBSOCKET_STATIC_STRING_HPP -#define BEAST_WEBSOCKET_STATIC_STRING_HPP +#ifndef BEAST_STATIC_STRING_HPP +#define BEAST_STATIC_STRING_HPP #include -#include -#include +#include +#include +#include #include +#include #include +#include #include #include +#include namespace beast { -/** A string with a fixed-size storage area. +/** A modifiable string with a fixed-size storage area. These objects behave like `std::string` except that the storage is not dynamically allocated but rather fixed in size. @@ -27,6 +31,8 @@ namespace beast { imposes a natural small upper limit on the size of a value. @note The stored string is always null-terminated. + + @see @ref to_static_string */ template< std::size_t N, @@ -37,10 +43,20 @@ class static_string template friend class static_string; + void + term() + { + Traits::assign(s_[n_], 0); + } + std::size_t n_; - std::array s_; + CharT s_[N+1]; public: + // + // Member types + // + using traits_type = Traits; using value_type = typename Traits::char_type; using size_type = std::size_t; @@ -56,35 +72,204 @@ public: using const_reverse_iterator = std::reverse_iterator; - /** Default constructor. + /// The type of `string_view` returned by the interface + using string_view_type = + beast::basic_string_view; - The string is initially empty, and null terminated. - */ + // + // Constants + // + + /// Maximum size of the string excluding the null terminator + static std::size_t constexpr max_size_n = N; + + /// A special index + static constexpr size_type npos = size_type(-1); + + // + // (constructor) + // + + /// Default constructor (empty string). static_string(); + /** Construct with count copies of character `ch`. + + The behavior is undefined if `count >= npos` + */ + static_string(size_type count, CharT ch); + + /// Construct with a substring (pos, other.size()) of `other`. + template + static_string(static_string const& other, + size_type pos); + + /// Construct with a substring (pos, count) of `other`. + template + static_string(static_string const& other, + size_type pos, size_type count); + + /// Construct with the first `count` characters of `s`, including nulls. + static_string(CharT const* s, size_type count); + + /// Construct from a null terminated string. + static_string(CharT const* s); + + /// Construct from a range of characters + template + static_string(InputIt first, InputIt last); + /// Copy constructor. - static_string(static_string const& s); + static_string(static_string const& other); /// Copy constructor. template - static_string(static_string const& s); + static_string(static_string const& other); + + /// Construct from an initializer list + static_string(std::initializer_list init); + + /// Construct from a `string_view` + explicit + static_string(string_view_type sv); + + /** Construct from any object convertible to `string_view_type`. + + The range (pos, n) is extracted from the value + obtained by converting `t` to `string_view_type`, + and used to construct the string. + */ +#if BEAST_DOXYGEN + template +#else + template::value>::type> +#endif + static_string(T const& t, size_type pos, size_type n); + + // + // (assignment) + // /// Copy assignment. static_string& - operator=(static_string const& s); + operator=(static_string const& str) + { + return assign(str); + } /// Copy assignment. template static_string& - operator=(static_string const& s); + operator=(static_string const& str) + { + return assign(str); + } - /// Construct from string literal. - template - static_string(const CharT (&s)[M]); + /// Assign from null-terminated string. + static_string& + operator=(CharT const* s) + { + return assign(s); + } - /// Assign from string literal. + /// Assign from single character. + static_string& + operator=(CharT ch) + { + return assign_char(ch, + std::integral_constant0)>{}); + } + + /// Assign from initializer list. + static_string& + operator=(std::initializer_list init) + { + return assign(init); + } + + /// Assign from `string_view_type`. + static_string& + operator=(string_view_type sv) + { + return assign(sv); + } + + /// Assign `count` copies of `ch`. + static_string& + assign(size_type count, CharT ch); + + /// Assign from another `static_string` + static_string& + assign(static_string const& str); + + // VFALCO NOTE this could come in two flavors, + // N>M and NM + + /// Assign from another `static_string` template - static_string& operator=(const CharT (&s)[M]); + static_string& + assign(static_string const& str) + { + return assign(str.data(), str.size()); + } + + /// Assign `count` characterss starting at `npos` from `other`. + template + static_string& + assign(static_string const& str, + size_type pos, size_type count = npos); + + /// Assign the first `count` characters of `s`, including nulls. + static_string& + assign(CharT const* s, size_type count); + + /// Assign a null terminated string. + static_string& + assign(CharT const* s) + { + return assign(s, Traits::length(s)); + } + + /// Assign from an iterator range of characters. + template + static_string& + assign(InputIt first, InputIt last); + + /// Assign from initializer list. + static_string& + assign(std::initializer_list init) + { + return assign(init.begin(), init.end()); + } + + /// Assign from `string_view_type`. + static_string& + assign(string_view_type str) + { + return assign(str.data(), str.size()); + } + + /** Assign from any object convertible to `string_view_type`. + + The range (pos, n) is extracted from the value + obtained by converting `t` to `string_view_type`, + and used to assign the string. + */ + template +#if BEAST_DOXYGEN + static_string& +#else + typename std::enable_if::value, static_string&>::type +#endif + assign(T const& t, + size_type pos, size_type count = npos); + + // + // Element access + // /// Access specified character with bounds checking. reference @@ -154,9 +339,20 @@ public: CharT const* c_str() const { - return &s_[0]; + return data(); } + /// Convert a static string to a `string_view_type` + operator string_view_type() const + { + return basic_string_view< + CharT, Traits>{data(), size()}; + } + + // + // Iterators + // + /// Returns an iterator to the beginning. iterator begin() @@ -241,6 +437,10 @@ public: return const_reverse_iterator{cbegin()}; } + // + // Capacity + // + /// Returns `true` if the string is empty. bool empty() const @@ -255,6 +455,13 @@ public: return n_; } + /// Returns the number of characters, excluding the null terminator. + size_type + length() const + { + return size(); + } + /// Returns the maximum number of characters that can be stored, excluding the null terminator. size_type constexpr max_size() const @@ -262,23 +469,323 @@ public: return N; } + /** Reserves storage. + + This actually just throws an exception if `n > N`, + otherwise does nothing since the storage is fixed. + */ + void + reserve(std::size_t n); + /// Returns the number of characters that can be held in currently allocated storage. - size_type + size_type constexpr capacity() const { - return N; + return max_size(); } + + /** Reduces memory usage by freeing unused memory. + + This actually does nothing, since the storage is fixed. + */ + void + shrink_to_fit() + { + } + + // + // Operations + // /// Clears the contents. void - clear() + clear(); + + static_string& + insert(size_type index, size_type count, CharT ch); + + static_string& + insert(size_type index, CharT const* s) { - resize(0); + return insert(index, s, Traits::length(s)); } + static_string& + insert(size_type index, CharT const* s, size_type count); + + template + static_string& + insert(size_type index, + static_string const& str) + { + return insert(index, str.data(), str.size()); + } + + template + static_string& + insert(size_type index, + static_string const& str, + size_type index_str, size_type count = npos); + + iterator + insert(const_iterator pos, CharT ch) + { + return insert(pos, 1, ch); + } + + iterator + insert(const_iterator pos, size_type count, CharT ch); + + template +#if BEAST_DOXYGEN + iterator +#else + typename std::enable_if< + detail::is_input_iterator::value, + iterator>::type +#endif + insert(const_iterator pos, InputIt first, InputIt last); + + iterator + insert(const_iterator pos, std::initializer_list init) + { + return insert(pos, init.begin(), init.end()); + } + + static_string& + insert(size_type index, string_view_type str) + { + return insert(index, str.data(), str.size()); + } + + template +#if BEAST_DOXYGEN + static_string& +#else + typename std::enable_if< + std::is_convertible::value && + ! std::is_convertible::value, + static_string&>::type +#endif + insert(size_type index, T const& t, + size_type index_str, size_type count = npos); + + static_string& + erase(size_type index = 0, size_type count = npos); + + iterator + erase(const_iterator pos); + + iterator + erase(const_iterator first, const_iterator last); + + void + push_back(CharT ch); + + void + pop_back() + { + Traits::assign(s_[--n_], 0); + } + + static_string& + append(size_type count, CharT ch) + { + insert(end(), count, ch); + return *this; + } + + template + static_string& + append(static_string const& str) + { + insert(size(), str); + return *this; + } + + template + static_string& + append(static_string const& str, + size_type pos, size_type count = npos); + + static_string& + append(CharT const* s, size_type count) + { + insert(size(), s, count); + return *this; + } + + static_string& + append(CharT const* s) + { + insert(size(), s); + return *this; + } + + template +#if BEAST_DOXYGEN + static_string& +#else + typename std::enable_if< + detail::is_input_iterator::value, + static_string&>::type +#endif + append(InputIt first, InputIt last) + { + insert(end(), first, last); + return *this; + } + + static_string& + append(std::initializer_list init) + { + insert(end(), init); + return *this; + } + + static_string& + append(string_view_type sv) + { + insert(size(), sv); + return *this; + } + + template + typename std::enable_if< + std::is_convertible::value && + ! std::is_convertible::value, + static_string&>::type + append(T const& t, size_type pos, size_type count = npos) + { + insert(size(), t, pos, count); + return *this; + } + + template + static_string& + operator+=(static_string const& str) + { + return append(str.data(), str.size()); + } + + static_string& + operator+=(CharT ch) + { + push_back(ch); + return *this; + } + + static_string& + operator+=(CharT const* s) + { + return append(s); + } + + static_string& + operator+=(std::initializer_list init) + { + return append(init); + } + + static_string& + operator+=(string_view_type const& str) + { + return append(str); + } + + template + int + compare(static_string const& str) const + { + return detail::lexicographical_compare( + &s_[0], n_, &str.s_[0], str.n_); + } + + template + int + compare(size_type pos1, size_type count1, + static_string const& str) const + { + return detail::lexicographical_compare( + substr(pos1, count1), str.data(), str.size()); + } + + template + int + compare(size_type pos1, size_type count1, + static_string const& str, + size_type pos2, size_type count2 = npos) const + { + return detail::lexicographical_compare( + substr(pos1, count1), str.substr(pos2, count2)); + } + + int + compare(CharT const* s) const + { + return detail::lexicographical_compare( + &s_[0], n_, s, Traits::length(s)); + } + + int + compare(size_type pos1, size_type count1, + CharT const* s) const + { + return detail::lexicographical_compare( + substr(pos1, count1), s, Traits::length(s)); + } + + int + compare(size_type pos1, size_type count1, + CharT const*s, size_type count2) const + { + return detail::lexicographical_compare( + substr(pos1, count1), s, count2); + } + + int + compare(string_view_type str) const + { + return detail::lexicographical_compare( + &s_[0], n_, str.data(), str.size()); + } + + int + compare(size_type pos1, size_type count1, + string_view_type str) const + { + return detail::lexicographical_compare( + substr(pos1, count1), str); + } + + template +#if BEAST_DOXYGEN + int +#else + typename std::enable_if< + std::is_convertible::value && + ! std::is_convertible::value, + int>::type +#endif + compare(size_type pos1, size_type count1, + T const& t, size_type pos2, + size_type count2 = npos) const + { + return compare(pos1, count1, + string_view_type(t).substr(pos2, count2)); + } + + string_view_type + substr(size_type pos = 0, size_type count = npos) const; + + /// Copy a substring (pos, pos+count) to character string pointed to by `dest`. + size_type + copy(CharT* dest, size_type count, size_type pos = 0) const; + /** Changes the number of characters stored. - @note No value-initialization is performed. + If the resulting string is larger, the new + characters are uninitialized. */ void resize(std::size_t n); @@ -291,231 +798,66 @@ public: void resize(std::size_t n, CharT c); - /// Compare two character sequences. - template - int - compare(static_string const& rhs) const; + /// Exchange the contents of this string with another. + void + swap(static_string& str); - /// Return the characters as a `basic_string`. - std::basic_string - to_string() const - { - return std::basic_string< - CharT, Traits>{&s_[0], n_}; - } + /// Exchange the contents of this string with another. + template + void + swap(static_string& str); + + // + // Search + // private: - void - assign(CharT const* s); + static_string& + assign_char(CharT ch, std::true_type); + + static_string& + assign_char(CharT ch, std::false_type); }; -template -static_string:: -static_string() - : n_(0) -{ - s_[0] = 0; -} +// +// Disallowed operations +// -template -static_string:: -static_string(static_string const& s) - : n_(s.n_) -{ - Traits::copy(&s_[0], &s.s_[0], n_ + 1); -} +// These operations are explicitly deleted since +// there is no reasonable implementation possible. -template -template -static_string:: -static_string(static_string const& s) -{ - if(s.size() > N) - throw detail::make_exception( - "static_string overflow", __FILE__, __LINE__); - n_ = s.size(); - Traits::copy(&s_[0], &s.s_[0], n_ + 1); -} - -template -auto -static_string:: -operator=(static_string const& s) -> - static_string& -{ - n_ = s.n_; - Traits::copy(&s_[0], &s.s_[0], n_ + 1); - return *this; -} - -template -template -auto -static_string:: -operator=(static_string const& s) -> - static_string& -{ - if(s.size() > N) - throw detail::make_exception( - "static_string overflow", __FILE__, __LINE__); - n_ = s.size(); - Traits::copy(&s_[0], &s.s_[0], n_ + 1); - return *this; -} - -template -template -static_string:: -static_string(const CharT (&s)[M]) - : n_(M-1) -{ - static_assert(M-1 <= N, - "static_string overflow"); - Traits::copy(&s_[0], &s[0], M); -} - -template -template -auto -static_string:: -operator=(const CharT (&s)[M]) -> - static_string& -{ - static_assert(M-1 <= N, - "static_string overflow"); - n_ = M-1; - Traits::copy(&s_[0], &s[0], M); - return *this; -} - -template -auto -static_string:: -at(size_type pos) -> - reference -{ - if(pos >= n_) - throw detail::make_exception( - "invalid pos", __FILE__, __LINE__); - return s_[pos]; -} - -template -auto -static_string:: -at(size_type pos) const -> - const_reference -{ - if(pos >= n_) - throw detail::make_exception( - "static_string::at", __FILE__, __LINE__); - return s_[pos]; -} +template +void +operator+( + static_stringconst& lhs, + static_stringconst& rhs) = delete; template void -static_string:: -resize(std::size_t n) -{ - if(n > N) - throw detail::make_exception( - "static_string overflow", __FILE__, __LINE__); - n_ = n; - s_[n_] = 0; -} +operator+(CharT const* lhs, + static_stringconst& rhs) = delete; template void -static_string:: -resize(std::size_t n, CharT c) -{ - if(n > N) - throw detail::make_exception( - "static_string overflow", __FILE__, __LINE__); - if(n > n_) - Traits::assign(&s_[n_], n - n_, c); - n_ = n; - s_[n_] = 0; -} - -template -template -int -static_string:: -compare(static_string const& rhs) const -{ - if(size() < rhs.size()) - { - auto const v = Traits::compare( - data(), rhs.data(), size()); - if(v == 0) - return -1; - return v; - } - else if(size() > rhs.size()) - { - auto const v = Traits::compare( - data(), rhs.data(), rhs.size()); - if(v == 0) - return 1; - return v; - } - return Traits::compare(data(), rhs.data(), size()); -} +operator+(CharT lhs, + static_string const& rhs) = delete; template void -static_string:: -assign(CharT const* s) -{ - auto const n = Traits::length(s); - if(n > N) - throw detail::make_exception( - "too large", __FILE__, __LINE__); - n_ = n; - Traits::copy(&s_[0], s, n_ + 1); -} +operator+(static_string const& lhs, + CharT const* rhs) = delete; -namespace detail { +template +void +operator+(static_string const& lhs, + CharT rhs) = delete; -template -int -compare( - static_string const& lhs, - const CharT (&s)[M]) -{ - if(lhs.size() < M-1) - { - auto const v = Traits::compare( - lhs.data(), &s[0], lhs.size()); - if(v == 0) - return -1; - return v; - } - else if(lhs.size() > M-1) - { - auto const v = Traits::compare( - lhs.data(), &s[0], M-1); - if(v == 0) - return 1; - return v; - } - return Traits::compare(lhs.data(), &s[0], lhs.size()); -} +// +// Non-member functions +// -template -inline -int -compare( - const CharT (&s)[M], - static_string const& rhs) -{ - return -compare(rhs, s); -} - -} // detail - -template +template bool operator==( static_string const& lhs, @@ -524,7 +866,8 @@ operator==( return lhs.compare(rhs) == 0; } -template +template bool operator!=( static_string const& lhs, @@ -533,7 +876,8 @@ operator!=( return lhs.compare(rhs) != 0; } -template +template bool operator<( static_string const& lhs, @@ -542,7 +886,8 @@ operator<( return lhs.compare(rhs) < 0; } -template +template bool operator<=( static_string const& lhs, @@ -551,7 +896,8 @@ operator<=( return lhs.compare(rhs) <= 0; } -template +template bool operator>( static_string const& lhs, @@ -560,7 +906,8 @@ operator>( return lhs.compare(rhs) > 0; } -template +template bool operator>=( static_string const& lhs, @@ -569,116 +916,193 @@ operator>=( return lhs.compare(rhs) >= 0; } -//--- - -template +template bool operator==( - const CharT (&s)[N], - static_string const& rhs) + CharT const* lhs, + static_string const& rhs) { - return detail::compare(s, rhs) == 0; + return detail::lexicographical_compare( + lhs, Traits::length(lhs), + rhs.data(), rhs.size()) == 0; } -template +template bool operator==( static_string const& lhs, - const CharT (&s)[M]) + CharT const* rhs) { - return detail::compare(lhs, s) == 0; + return detail::lexicographical_compare( + lhs.data(), lhs.size(), + rhs, Traits::length(rhs)) == 0; } -template +template bool operator!=( - const CharT (&s)[N], - static_string const& rhs) + CharT const* lhs, + static_string const& rhs) { - return detail::compare(s, rhs) != 0; + return detail::lexicographical_compare( + lhs, Traits::length(lhs), + rhs.data(), rhs.size()) != 0; } -template +template bool operator!=( static_string const& lhs, - const CharT (&s)[M]) + CharT const* rhs) { - return detail::compare(lhs, s) != 0; + return detail::lexicographical_compare( + lhs.data(), lhs.size(), + rhs, Traits::length(rhs)) != 0; } -template +template bool operator<( - const CharT (&s)[N], - static_string const& rhs) + CharT const* lhs, + static_string const& rhs) { - return detail::compare(s, rhs) < 0; + return detail::lexicographical_compare( + lhs, Traits::length(lhs), + rhs.data(), rhs.size()) < 0; } -template +template bool operator<( static_string const& lhs, - const CharT (&s)[M]) + CharT const* rhs) { - return detail::compare(lhs, s) < 0; + return detail::lexicographical_compare( + lhs.data(), lhs.size(), + rhs, Traits::length(rhs)) < 0; } -template +template bool operator<=( - const CharT (&s)[N], - static_string const& rhs) + CharT const* lhs, + static_string const& rhs) { - return detail::compare(s, rhs) <= 0; + return detail::lexicographical_compare( + lhs, Traits::length(lhs), + rhs.data(), rhs.size()) <= 0; } -template +template bool operator<=( static_string const& lhs, - const CharT (&s)[M]) + CharT const* rhs) { - return detail::compare(lhs, s) <= 0; + return detail::lexicographical_compare( + lhs.data(), lhs.size(), + rhs, Traits::length(rhs)) <= 0; } -template +template bool operator>( - const CharT (&s)[N], - static_string const& rhs) + CharT const* lhs, + static_string const& rhs) { - return detail::compare(s, rhs) > 0; + return detail::lexicographical_compare( + lhs, Traits::length(lhs), + rhs.data(), rhs.size()) > 0; } -template +template bool operator>( static_string const& lhs, - const CharT (&s)[M]) + CharT const* rhs) { - return detail::compare(lhs, s) > 0; + return detail::lexicographical_compare( + lhs.data(), lhs.size(), + rhs, Traits::length(rhs)) > 0; } -template +template bool operator>=( - const CharT (&s)[N], - static_string const& rhs) + CharT const* lhs, + static_string const& rhs) { - return detail::compare(s, rhs) >= 0; + return detail::lexicographical_compare( + lhs, Traits::length(lhs), + rhs.data(), rhs.size()) >= 0; } -template +template bool operator>=( static_string const& lhs, - const CharT (&s)[M]) + CharT const* rhs) { - return detail::compare(lhs, s) >= 0; + return detail::lexicographical_compare( + lhs.data(), lhs.size(), + rhs, Traits::length(rhs)) >= 0; } +// +// swap +// + +template +void +swap( + static_string& lhs, + static_string& rhs) +{ + lhs.swap(rhs); +} + +template +void +swap( + static_string& lhs, + static_string& rhs) +{ + lhs.swap(rhs); +} + +// +// Input/Output +// + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, + static_string const& str) +{ + return os << static_cast< + beast::basic_string_view>(str); +} + +// +// Numeric conversions +// + +/** Returns a static string representing an integer as a decimal. + + @param x The signed or unsigned integer to convert. + This must be an integral type. + + @return A @ref static_string with an implementation defined + maximum size large enough to hold the longest possible decimal + representation of any integer of the given type. +*/ +template +static_string +to_static_string(Integer x); + } // beast +#include + #endif diff --git a/include/beast/core/stream_concepts.hpp b/include/beast/core/stream_concepts.hpp deleted file mode 100644 index 5f0ba3adfa..0000000000 --- a/include/beast/core/stream_concepts.hpp +++ /dev/null @@ -1,77 +0,0 @@ -// -// 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_STREAM_CONCEPTS_HPP -#define BEAST_STREAM_CONCEPTS_HPP - -#include -#include -#include - -namespace beast { - -/// Determine if `T` has the `get_io_service` member. -template -#if GENERATING_DOCS -struct has_get_io_service : std::integral_constant{}; -#else -using has_get_io_service = typename detail::has_get_io_service::type; -#endif - -/// Determine if `T` meets the requirements of @b `AsyncReadStream`. -template -#if GENERATING_DOCS -struct is_AsyncReadStream : std::integral_constant{}; -#else -using is_AsyncReadStream = typename detail::is_AsyncReadStream::type; -#endif - -/// Determine if `T` meets the requirements of @b `AsyncWriteStream`. -template -#if GENERATING_DOCS -struct is_AsyncWriteStream : std::integral_constant{}; -#else -using is_AsyncWriteStream = typename detail::is_AsyncWriteStream::type; -#endif - -/// Determine if `T` meets the requirements of @b `SyncReadStream`. -template -#if GENERATING_DOCS -struct is_SyncReadStream : std::integral_constant{}; -#else -using is_SyncReadStream = typename detail::is_SyncReadStream::type; -#endif - -/// Determine if `T` meets the requirements of @b `SyncWriterStream`. -template -#if GENERATING_DOCS -struct is_SyncWriteStream : std::integral_constant{}; -#else -using is_SyncWriteStream = typename detail::is_SyncWriteStream::type; -#endif - -/// Determine if `T` meets the requirements of @b `AsyncStream`. -template -#if GENERATING_DOCS -struct is_AsyncStream : std::integral_constant{}; -#else -using is_AsyncStream = std::integral_constant::value && is_AsyncWriteStream::value>; -#endif - -/// Determine if `T` meets the requirements of @b `SyncStream`. -template -#if GENERATING_DOCS -struct is_SyncStream : std::integral_constant{}; -#else -using is_SyncStream = std::integral_constant::value && is_SyncWriteStream::value>; -#endif - -} // beast - -#endif diff --git a/include/beast/core/streambuf.hpp b/include/beast/core/streambuf.hpp deleted file mode 100644 index 537094c1a4..0000000000 --- a/include/beast/core/streambuf.hpp +++ /dev/null @@ -1,345 +0,0 @@ -// -// 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_STREAMBUF_HPP -#define BEAST_STREAMBUF_HPP - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { - -/** A @b `DynamicBuffer` that uses multiple buffers internally. - - The implementation uses a sequence of one or more character arrays - of varying sizes. Additional character array objects are appended to - the sequence to accommodate changes in the size of the character - sequence. - - @note Meets the requirements of @b DynamicBuffer. - - @tparam Allocator The allocator to use for managing memory. -*/ -template -class basic_streambuf -#if ! GENERATING_DOCS - : private detail::empty_base_optimization< - typename std::allocator_traits:: - template rebind_alloc> -#endif -{ -public: -#if GENERATING_DOCS - /// The type of allocator used. - using allocator_type = Allocator; -#else - using allocator_type = typename - std::allocator_traits:: - template rebind_alloc; -#endif - -private: - // Storage for the list of buffers representing the input - // and output sequences. The allocation for each element - // contains `element` followed by raw storage bytes. - class element; - - using alloc_traits = std::allocator_traits; - using list_type = typename boost::intrusive::make_list>::type; - using iterator = typename list_type::iterator; - using const_iterator = typename list_type::const_iterator; - - using size_type = typename std::allocator_traits::size_type; - using const_buffer = boost::asio::const_buffer; - using mutable_buffer = boost::asio::mutable_buffer; - - static_assert(std::is_base_of::iterator_category>::value, - "BidirectionalIterator requirements not met"); - - static_assert(std::is_base_of::iterator_category>::value, - "BidirectionalIterator requirements not met"); - - list_type list_; // list of allocated buffers - iterator out_; // element that contains out_pos_ - size_type alloc_size_; // min amount to allocate - size_type in_size_ = 0; // size of the input sequence - size_type in_pos_ = 0; // input offset in list_.front() - size_type out_pos_ = 0; // output offset in *out_ - size_type out_end_ = 0; // output end offset in list_.back() - -public: -#if GENERATING_DOCS - /// The type used to represent the input sequence as a list of buffers. - using const_buffers_type = implementation_defined; - - /// The type used to represent the output sequence as a list of buffers. - using mutable_buffers_type = implementation_defined; - -#else - class const_buffers_type; - - class mutable_buffers_type; - -#endif - - /// Destructor. - ~basic_streambuf(); - - /** Move constructor. - - The new object will have the input sequence of - the other stream buffer, and an empty output sequence. - - @note After the move, the moved-from object will have - an empty input and output sequence, with no internal - buffers allocated. - */ - basic_streambuf(basic_streambuf&&); - - /** Move constructor. - - The new object will have the input sequence of - the other stream buffer, and an empty output sequence. - - @note After the move, the moved-from object will have - an empty input and output sequence, with no internal - buffers allocated. - - @param alloc The allocator to associate with the - stream buffer. - */ - basic_streambuf(basic_streambuf&&, - allocator_type const& alloc); - - /** Move assignment. - - This object will have the input sequence of - the other stream buffer, and an empty output sequence. - - @note After the move, the moved-from object will have - an empty input and output sequence, with no internal - buffers allocated. - */ - basic_streambuf& - operator=(basic_streambuf&&); - - /** Copy constructor. - - This object will have a copy of the other stream - buffer's input sequence, and an empty output sequence. - */ - basic_streambuf(basic_streambuf const&); - - /** Copy constructor. - - This object will have a copy of the other stream - buffer's input sequence, and an empty output sequence. - - @param alloc The allocator to associate with the - stream buffer. - */ - basic_streambuf(basic_streambuf const&, - allocator_type const& alloc); - - /** Copy assignment. - - This object will have a copy of the other stream - buffer's input sequence, and an empty output sequence. - */ - basic_streambuf& operator=(basic_streambuf const&); - - /** Copy constructor. - - This object will have a copy of the other stream - buffer's input sequence, and an empty output sequence. - */ - template - basic_streambuf(basic_streambuf const&); - - /** Copy constructor. - - This object will have a copy of the other stream - buffer's input sequence, and an empty output sequence. - - @param alloc The allocator to associate with the - stream buffer. - */ - template - basic_streambuf(basic_streambuf const&, - allocator_type const& alloc); - - /** Copy assignment. - - This object will have a copy of the other stream - buffer's input sequence, and an empty output sequence. - */ - template - basic_streambuf& operator=(basic_streambuf const&); - - /** Construct a stream buffer. - - @param alloc_size The size of buffer to allocate. This is a - soft limit, calls to prepare for buffers exceeding this size - will allocate the larger size. The default allocation size - is 1KB (1024 bytes). - - @param alloc The allocator to use. If this parameter is - unspecified, a default constructed allocator will be used. - */ - explicit - basic_streambuf(std::size_t alloc_size = 1024, - Allocator const& alloc = allocator_type{}); - - /// Returns a copy of the associated allocator. - allocator_type - get_allocator() const - { - return this->member(); - } - - /** Returns the default allocation size. - - This is the smallest size that the stream buffer will allocate. - The size of the allocation can influence capacity, which will - affect algorithms that use capacity to efficiently read from - streams. - */ - std::size_t - alloc_size() const - { - return alloc_size_; - } - - /** Set the default allocation size. - - This is the smallest size that the stream buffer will allocate. - The size of the allocation can influence capacity, which will - affect algorithms that use capacity to efficiently read from - streams. - - @note This will not affect any already-existing allocations. - - @param n The number of bytes. - */ - void - alloc_size(std::size_t n) - { - alloc_size_ = n; - } - - /// Returns the size of the input sequence. - size_type - size() const - { - return in_size_; - } - - /// Returns the permitted maximum sum of the sizes of the input and output sequence. - size_type - max_size() const - { - return (std::numeric_limits::max)(); - } - - /// Returns the maximum sum of the sizes of the input sequence and output sequence the buffer can hold without requiring reallocation. - std::size_t - capacity() const; - - /** Get a list of buffers that represents the input sequence. - - @note These buffers remain valid across subsequent calls to `prepare`. - */ - const_buffers_type - data() const; - - /** Get a list of buffers that represents the output sequence, with the given size. - - @note Buffers representing the input sequence acquired prior to - this call remain valid. - */ - mutable_buffers_type - prepare(size_type n); - - /** Move bytes from the output sequence to the input sequence. - - @note Buffers representing the input sequence acquired prior to - this call remain valid. - */ - void - commit(size_type n); - - /// Remove bytes from the input sequence. - void - consume(size_type n); - - // Helper for boost::asio::read_until - template - friend - std::size_t - read_size_helper(basic_streambuf< - OtherAllocator> const& streambuf, std::size_t max_size); - -private: - void - clear(); - - void - move_assign(basic_streambuf& other, std::false_type); - - void - move_assign(basic_streambuf& other, std::true_type); - - void - copy_assign(basic_streambuf const& other, std::false_type); - - void - copy_assign(basic_streambuf const& other, std::true_type); - - void - delete_list(); - - void - debug_check() const; -}; - -/** A @b `DynamicBuffer` that uses multiple buffers internally. - - The implementation uses a sequence of one or more character arrays - of varying sizes. Additional character array objects are appended to - the sequence to accommodate changes in the size of the character - sequence. - - @note Meets the requirements of @b `DynamicBuffer`. -*/ -using streambuf = basic_streambuf>; - -/** Format output to a @ref basic_streambuf. - - @param streambuf The @ref basic_streambuf to write to. - - @param t The object to write. - - @return A reference to the @ref basic_streambuf. -*/ -template -basic_streambuf& -operator<<(basic_streambuf& streambuf, T const& t); - -} // beast - -#include - -#endif diff --git a/include/beast/core/string.hpp b/include/beast/core/string.hpp new file mode 100644 index 0000000000..601160982b --- /dev/null +++ b/include/beast/core/string.hpp @@ -0,0 +1,151 @@ +// +// 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_STRING_HPP +#define BEAST_STRING_HPP + +#include +#include +#ifndef BEAST_NO_BOOST_STRING_VIEW +# if BOOST_VERSION >= 106400 +# define BEAST_NO_BOOST_STRING_VIEW 0 +# else +# define BEAST_NO_BOOST_STRING_VIEW 1 +# endif +#endif + +#if BEAST_NO_BOOST_STRING_VIEW +#include +#else +#include +#endif + +#include + +namespace beast { + +#if BEAST_NO_BOOST_STRING_VIEW +/// The type of string view used by the library +using string_view = boost::string_ref; + +/// The type of basic string view used by the library +template +using basic_string_view = + boost::basic_string_ref; +#else +/// The type of string view used by the library +using string_view = boost::string_view; + +/// The type of basic string view used by the library +template +using basic_string_view = + boost::basic_string_view; +#endif + +namespace detail { + +inline +char +ascii_tolower(char c) +{ + if(c >= 'A' && c <= 'Z') + c += 'a' - 'A'; + return c; +} + +template +bool +iequals( + beast::string_view const& lhs, + beast::string_view const& rhs) +{ + auto n = lhs.size(); + if(rhs.size() != n) + return false; + auto p1 = lhs.data(); + auto p2 = rhs.data(); + char a, b; + while(n--) + { + a = *p1++; + b = *p2++; + if(a != b) + goto slow; + } + return true; + + while(n--) + { + slow: + if(ascii_tolower(a) != ascii_tolower(b)) + return false; + a = *p1++; + b = *p2++; + } + return true; +} + +} // detail + +/** Returns `true` if two strings are equal, using a case-insensitive comparison. + + The case-comparison operation is defined only for low-ASCII characters. + + @param lhs The string on the left side of the equality + + @param rhs The string on the right side of the equality +*/ +inline +bool +iequals( + beast::string_view const& lhs, + beast::string_view const& rhs) +{ + return detail::iequals(lhs, rhs); +} + +/** A case-insensitive less predicate for strings. + + The case-comparison operation is defined only for low-ASCII characters. +*/ +struct iless +{ + bool + operator()( + string_view const& lhs, + string_view const& rhs) const + { + using std::begin; + using std::end; + return std::lexicographical_compare( + begin(lhs), end(lhs), begin(rhs), end(rhs), + [](char c1, char c2) + { + return detail::ascii_tolower(c1) < detail::ascii_tolower(c2); + } + ); + } +}; + +/** A case-insensitive equality predicate for strings. + + The case-comparison operation is defined only for low-ASCII characters. +*/ +struct iequal +{ + bool + operator()( + string_view const& lhs, + string_view const& rhs) const + { + return iequals(lhs, rhs); + } +}; + +} // beast + +#endif diff --git a/include/beast/core/string_param.hpp b/include/beast/core/string_param.hpp new file mode 100644 index 0000000000..dedd060aae --- /dev/null +++ b/include/beast/core/string_param.hpp @@ -0,0 +1,109 @@ +// +// 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_STRING_PARAM_HPP +#define BEAST_STRING_PARAM_HPP + +#include +#include +#include +#include +#include +#include + +namespace beast { + +/** A function parameter which efficiently converts to string. + + This is used as a function parameter type to allow callers + notational convenience: objects other than strings may be + passed in contexts where a string is expected. The conversion + to string is made using `operator<<` to a non-dynamically + allocated static buffer if possible, else to a `std::string` + on overflow. +*/ +class string_param +{ + string_view sv_; + char buf_[128]; + boost::optional os_; + + template + typename std::enable_if< + std::is_integral::value>::type + print(T const&); + + template + typename std::enable_if< + ! std::is_integral::value && + ! std::is_convertible::value + >::type + print(T const&); + + void + print(string_view const&); + + template + typename std::enable_if< + std::is_integral::value>::type + print_1(T const&); + + template + typename std::enable_if< + ! std::is_integral::value>::type + print_1(T const&); + + void + print_n() + { + } + + template + void + print_n(T0 const&, TN const&...); + + template + void + print(T0 const&, T1 const&, TN const&...); + +public: + /// Copy constructor (disallowed) + string_param(string_param const&) = delete; + + /// Copy assignment (disallowed) + string_param& operator=(string_param const&) = delete; + + /** Constructor + + This function constructs a string as if by concatenating + the result of streaming each argument in order into an + output stream. It is used as a notational convenience + at call sites which expect a parameter with the semantics + of a @ref string_view. + + The implementation uses a small, internal static buffer + to avoid memory allocations especially for the case where + the list of arguments to be converted consists of a single + integral type. + + @param args One or more arguments to convert + */ + template + string_param(Args const&... args); + + /// Implicit conversion to @ref string_view + operator string_view const() const + { + return sv_; + } +}; + +} // beast + +#include + +#endif diff --git a/include/beast/core/to_string.hpp b/include/beast/core/to_string.hpp deleted file mode 100644 index e391ea2d57..0000000000 --- a/include/beast/core/to_string.hpp +++ /dev/null @@ -1,53 +0,0 @@ -// -// 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_TO_STRING_HPP -#define BEAST_TO_STRING_HPP - -#include -#include -#include -#include - -namespace beast { - -/** Convert a @b `ConstBufferSequence` to a `std::string`. - - This function will convert the octets in a buffer sequence to a string. - All octets will be inserted into the resulting string, including null - or unprintable characters. - - @param buffers The buffer sequence to convert. - - @return A string representing the contents of the input area. - - @note This function participates in overload resolution only if - the buffers parameter meets the requirements of @b `ConstBufferSequence`. -*/ -template -#if GENERATING_DOCS -std::string -#else -typename std::enable_if< - is_ConstBufferSequence::value, - std::string>::type -#endif -to_string(ConstBufferSequence const& buffers) -{ - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - std::string s; - s.reserve(buffer_size(buffers)); - for(auto const& buffer : buffers) - s.append(buffer_cast(buffer), - buffer_size(buffer)); - return s; -} - -} // beast - -#endif diff --git a/include/beast/core/type_traits.hpp b/include/beast/core/type_traits.hpp new file mode 100644 index 0000000000..2624ee6680 --- /dev/null +++ b/include/beast/core/type_traits.hpp @@ -0,0 +1,633 @@ +// +// 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_TYPE_TRAITS_HPP +#define BEAST_TYPE_TRAITS_HPP + +#include +#include +#include +#include +#include + +namespace beast { + +//------------------------------------------------------------------------------ +// +// Buffer concepts +// +//------------------------------------------------------------------------------ + +/** Determine if `T` meets the requirements of @b ConstBufferSequence. + + Metafunctions are used to perform compile time checking of template + types. This type will be `std::true_type` if `T` meets the requirements, + else the type will be `std::false_type`. + + @par Example + + Use with `static_assert`: + + @code + template + void f(ConstBufferSequence const& buffers) + { + static_assert(is_const_buffer_sequence::value, + "ConstBufferSequence requirements not met"); + ... + @endcode + + Use with `std::enable_if` (SFINAE): + + @code + template + typename std::enable_if::value>::type + f(ConstBufferSequence const& buffers); + @endcode +*/ +template +#if BEAST_DOXYGEN +struct is_const_buffer_sequence : std::integral_constant +#else +struct is_const_buffer_sequence : + detail::is_buffer_sequence +#endif +{ +}; + +/** Determine if `T` meets the requirements of @b MutableBufferSequence. + + Metafunctions are used to perform compile time checking of template + types. This type will be `std::true_type` if `T` meets the requirements, + else the type will be `std::false_type`. + + @par Example + + Use with `static_assert`: + + @code + template + void f(MutableBufferSequence const& buffers) + { + static_assert(is_const_buffer_sequence::value, + "MutableBufferSequence requirements not met"); + ... + @endcode + + Use with `std::enable_if` (SFINAE): + + @code + template + typename std::enable_if::value>::type + f(MutableBufferSequence const& buffers); + @endcode +*/ +template +#if BEAST_DOXYGEN +struct is_mutable_buffer_sequence : std::integral_constant +#else +struct is_mutable_buffer_sequence : + detail::is_buffer_sequence +#endif +{ +}; + +/** Determine if `T` meets the requirements of @b DynamicBuffer. + + Metafunctions are used to perform compile time checking of template + types. This type will be `std::true_type` if `T` meets the requirements, + else the type will be `std::false_type`. + + @par Example + + Use with `static_assert`: + + @code + template + void f(DynamicBuffer& buffer) + { + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + ... + @endcode + + Use with `std::enable_if` (SFINAE): + + @code + template + typename std::enable_if::value>::type + f(DynamicBuffer const& buffer); + @endcode +*/ +#if BEAST_DOXYGEN +template +struct is_dynamic_buffer : std::integral_constant {}; +#else +template +struct is_dynamic_buffer : std::false_type {}; + +template +struct is_dynamic_buffer() = + std::declval().size(), + std::declval() = + std::declval().max_size(), + std::declval() = + std::declval().capacity(), + std::declval().commit(std::declval()), + std::declval().consume(std::declval()), + (void)0)> > : std::integral_constant::value && + is_mutable_buffer_sequence< + typename T::mutable_buffers_type>::value && + std::is_same().data())>::value && + std::is_same().prepare( + std::declval()))>::value + > +{ +}; + +// Special case for Boost.Asio which doesn't adhere to +// net-ts but still provides a read_size_helper so things work +template +struct is_dynamic_buffer< + boost::asio::basic_streambuf> : std::true_type +{ +}; +#endif + +//------------------------------------------------------------------------------ +// +// Handler concepts +// +//------------------------------------------------------------------------------ + +/** Determine if `T` meets the requirements of @b CompletionHandler. + + This trait checks whether a type meets the requirements for a completion + handler, and is also callable with the specified signature. + Metafunctions are used to perform compile time checking of template + types. This type will be `std::true_type` if `T` meets the requirements, + else the type will be `std::false_type`. + + @par Example + + Use with `static_assert`: + + @code + struct handler + { + void operator()(error_code&); + }; + + static_assert(is_completion_handler::value, + "Not a completion handler"); + @endcode +*/ +template +#if BEAST_DOXYGEN +using is_completion_handler = std::integral_constant; +#else +using is_completion_handler = std::integral_constant::type>::value && + detail::is_invocable::value>; +#endif + +//------------------------------------------------------------------------------ +// +// Stream concepts +// +//------------------------------------------------------------------------------ + +/** Determine if `T` has the `get_io_service` member. + + Metafunctions are used to perform compile time checking of template + types. This type will be `std::true_type` if `T` has the member + function with the correct signature, else type will be `std::false_type`. + + @par Example + + Use with tag dispatching: + + @code + template + void maybe_hello(T& t, std::true_type) + { + t.get_io_service().post([]{ std::cout << "Hello, world!" << std::endl; }); + } + + template + void maybe_hello(T&, std::false_type) + { + // T does not have get_io_service + } + + template + void maybe_hello(T& t) + { + maybe_hello(t, has_get_io_service{}); + } + @endcode + + Use with `static_assert`: + + @code + struct stream + { + boost::asio::io_service& get_io_service(); + }; + + static_assert(has_get_io_service::value, + "Missing get_io_service member"); + @endcode +*/ +#if BEAST_DOXYGEN +template +struct has_get_io_service : std::integral_constant{}; +#else +template +struct has_get_io_service : std::false_type {}; + +template +struct has_get_io_service( + std::declval().get_io_service()), + (void)0)>> : std::true_type {}; +#endif + +/** Returns `T::lowest_layer_type` if it exists, else `T` + + This will contain a nested `type` equal to `T::lowest_layer_type` + if it exists, else `type` will be equal to `T`. + + @par Example + + Declaring a wrapper: + + @code + template + struct stream_wrapper + { + using next_layer_type = typename std::remove_reference::type; + using lowest_layer_type = typename get_lowest_layer::type; + }; + @endcode + + Defining a metafunction: + + @code + /// Alias for `std::true_type` if `T` wraps another stream + template + using is_stream_wrapper : std::integral_constant::type>::value> {}; + @endcode +*/ +#if BEAST_DOXYGEN +template +struct get_lowest_layer; +#else +template +struct get_lowest_layer +{ + using type = T; +}; + +template +struct get_lowest_layer> +{ + using type = typename T::lowest_layer_type; +}; +#endif + +/** Determine if `T` meets the requirements of @b AsyncReadStream. + + Metafunctions are used to perform compile time checking of template + types. This type will be `std::true_type` if `T` meets the requirements, + else the type will be `std::false_type`. + + @par Example + + Use with `static_assert`: + + @code + template + void f(AsyncReadStream& stream) + { + static_assert(is_async_read_stream::value, + "AsyncReadStream requirements not met"); + ... + @endcode + + Use with `std::enable_if` (SFINAE): + + @code + template + typename std::enable_if::value>::type + f(AsyncReadStream& stream); + @endcode +*/ +#if BEAST_DOXYGEN +template +struct is_async_read_stream : std::integral_constant{}; +#else +template +struct is_async_read_stream : std::false_type {}; + +template +struct is_async_read_stream().async_read_some( + std::declval(), + std::declval()), + (void)0)>> : std::integral_constant::value + > {}; +#endif + +/** Determine if `T` meets the requirements of @b AsyncWriteStream. + + Metafunctions are used to perform compile time checking of template + types. This type will be `std::true_type` if `T` meets the requirements, + else the type will be `std::false_type`. + + @par Example + + Use with `static_assert`: + + @code + template + void f(AsyncWriteStream& stream) + { + static_assert(is_async_write_stream::value, + "AsyncWriteStream requirements not met"); + ... + @endcode + + Use with `std::enable_if` (SFINAE): + + @code + template + typename std::enable_if::value>::type + f(AsyncWriteStream& stream); + @endcode +*/ +#if BEAST_DOXYGEN +template +struct is_async_write_stream : std::integral_constant{}; +#else +template +struct is_async_write_stream : std::false_type {}; + +template +struct is_async_write_stream().async_write_some( + std::declval(), + std::declval()), + (void)0)>> : std::integral_constant::value + > {}; +#endif + +/** Determine if `T` meets the requirements of @b SyncReadStream. + + Metafunctions are used to perform compile time checking of template + types. This type will be `std::true_type` if `T` meets the requirements, + else the type will be `std::false_type`. + + @par Example + + Use with `static_assert`: + + @code + template + void f(SyncReadStream& stream) + { + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + ... + @endcode + + Use with `std::enable_if` (SFINAE): + + @code + template + typename std::enable_if::value>::type + f(SyncReadStream& stream); + @endcode +*/ +#if BEAST_DOXYGEN +template +struct is_sync_read_stream : std::integral_constant{}; +#else +template +struct is_sync_read_stream : std::false_type {}; + +template +struct is_sync_read_stream() = std::declval().read_some( + std::declval()), + std::declval() = std::declval().read_some( + std::declval(), + std::declval()), + (void)0)>> : std::integral_constant::value + > {}; +#endif + +/** Determine if `T` meets the requirements of @b SyncWriterStream. + + Metafunctions are used to perform compile time checking of template + types. This type will be `std::true_type` if `T` meets the requirements, + else the type will be `std::false_type`. + + @par Example + + Use with `static_assert`: + + @code + template + void f(SyncReadStream& stream) + { + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + ... + @endcode + + Use with `std::enable_if` (SFINAE): + + @code + template + typename std::enable_if::value>::type + f(SyncReadStream& stream); + @endcode +*/ +#if BEAST_DOXYGEN +template +struct is_sync_write_stream : std::integral_constant{}; +#else +template +struct is_sync_write_stream : std::false_type {}; + +template +struct is_sync_write_stream() = std::declval().write_some( + std::declval()), + std::declval() = std::declval().write_some( + std::declval(), + std::declval()), + (void)0)>> : std::integral_constant::value + > {}; +#endif + +/** Determine if `T` meets the requirements of @b AsyncStream. + + Metafunctions are used to perform compile time checking of template + types. This type will be `std::true_type` if `T` meets the requirements, + else the type will be `std::false_type`. + + @par Example + + Use with `static_assert`: + + @code + template + void f(AsyncStream& stream) + { + static_assert(is_async_stream::value, + "AsyncStream requirements not met"); + ... + @endcode + + Use with `std::enable_if` (SFINAE): + + @code + template + typename std::enable_if::value>::type + f(AsyncStream& stream); + @endcode +*/ +#if BEAST_DOXYGEN +template +struct is_async_stream : std::integral_constant{}; +#else +template +using is_async_stream = std::integral_constant::value && is_async_write_stream::value>; +#endif + +/** Determine if `T` meets the requirements of @b SyncStream. + + Metafunctions are used to perform compile time checking of template + types. This type will be `std::true_type` if `T` meets the requirements, + else the type will be `std::false_type`. + + @par Example + + Use with `static_assert`: + + @code + template + void f(SyncStream& stream) + { + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + ... + @endcode + + Use with `std::enable_if` (SFINAE): + + @code + template + typename std::enable_if::value>::type + f(SyncStream& stream); + @endcode +*/ +#if BEAST_DOXYGEN +template +struct is_sync_stream : std::integral_constant{}; +#else +template +using is_sync_stream = std::integral_constant::value && is_sync_write_stream::value>; +#endif + +//------------------------------------------------------------------------------ +// +// File concepts +// +//------------------------------------------------------------------------------ + +/** Determine if `T` meets the requirements of @b File. + + Metafunctions are used to perform compile time checking of template + types. This type will be `std::true_type` if `T` meets the requirements, + else the type will be `std::false_type`. + + @par Example + + Use with `static_assert`: + + @code + template + void f(File& file) + { + static_assert(is_file::value, + "File requirements not met"); + ... + @endcode + + Use with `std::enable_if` (SFINAE): + + @code + template + typename std::enable_if::value>::type + f(File& file); + @endcode +*/ +#if BEAST_DOXYGEN +template +struct is_file : std::integral_constant{}; +#else +template +struct is_file : std::false_type {}; + +template +struct is_file() = std::declval().is_open(), + std::declval().close(std::declval()), + std::declval().open( + std::declval(), + std::declval(), + std::declval()), + std::declval() = std::declval().size( + std::declval()), + std::declval() = std::declval().pos( + std::declval()), + std::declval().seek( + std::declval(), + std::declval()), + std::declval() = std::declval().read( + std::declval(), + std::declval(), + std::declval()), + std::declval() = std::declval().write( + std::declval(), + std::declval(), + std::declval()), + (void)0)>> : std::integral_constant::value && + std::is_destructible::value + > {}; +#endif + +} // beast + +#endif diff --git a/include/beast/core/write_dynabuf.hpp b/include/beast/core/write_dynabuf.hpp deleted file mode 100644 index 646e787498..0000000000 --- a/include/beast/core/write_dynabuf.hpp +++ /dev/null @@ -1,64 +0,0 @@ -// -// 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_WRITE_DYNABUF_HPP -#define BEAST_WRITE_DYNABUF_HPP - -#include -#include -#include -#include -#include - -namespace beast { - -/** Write to a @b `DynamicBuffer`. - - This function appends the serialized representation of each provided - argument into the dynamic buffer. It is capable of converting the - following types of arguments: - - @li `boost::asio::const_buffer` - - @li `boost::asio::mutable_buffer` - - @li A type meeting the requirements of @b `ConvertibleToConstBuffer` - - @li A type meeting the requirements of @b `ConstBufferSequence` - - @li A type meeting the requirements of @b `MutableBufferSequence` - - For all types not listed above, the function will invoke - `boost::lexical_cast` on the argument in an attempt to convert to - a string, which is then appended to the dynamic buffer. - - When this function serializes numbers, it converts them to - their text representation as if by a call to `std::to_string`. - - @param dynabuf The dynamic buffer to write to. - - @param args A list of one or more arguments to write. - - @throws unspecified Any exceptions thrown by `boost::lexical_cast`. - - @note This function participates in overload resolution only if - the `dynabuf` parameter meets the requirements of @b `DynamicBuffer`. -*/ -template -#if GENERATING_DOCS -void -#else -typename std::enable_if::value>::type -#endif -write(DynamicBuffer& dynabuf, Args const&... args) -{ - detail::write_dynabuf(dynabuf, args...); -} - -} // beast - -#endif diff --git a/include/beast/http.hpp b/include/beast/http.hpp index b4c7734f17..4f13500e4f 100644 --- a/include/beast/http.hpp +++ b/include/beast/http.hpp @@ -10,20 +10,25 @@ #include -#include -#include -#include +#include +#include +#include #include +#include +#include #include +#include #include -#include -#include -#include +#include #include -#include #include -#include +#include +#include +#include #include +#include +#include +#include #include #endif diff --git a/include/beast/http/basic_dynabuf_body.hpp b/include/beast/http/basic_dynabuf_body.hpp deleted file mode 100644 index a2c090149e..0000000000 --- a/include/beast/http/basic_dynabuf_body.hpp +++ /dev/null @@ -1,101 +0,0 @@ -// -// 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_HTTP_BASIC_DYNABUF_BODY_HPP -#define BEAST_HTTP_BASIC_DYNABUF_BODY_HPP - -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -/** A message body represented by a @b `DynamicBuffer` - - Meets the requirements of @b `Body`. -*/ -template -struct basic_dynabuf_body -{ - /// The type of the `message::body` member - using value_type = DynamicBuffer; - -#if GENERATING_DOCS -private: -#endif - - class reader - { - value_type& sb_; - - public: - template - explicit - reader(message& m) noexcept - : sb_(m.body) - { - } - - void - init(error_code&) noexcept - { - } - - void - write(void const* data, - std::size_t size, error_code&) noexcept - { - using boost::asio::buffer; - using boost::asio::buffer_copy; - sb_.commit(buffer_copy( - sb_.prepare(size), buffer(data, size))); - } - }; - - class writer - { - DynamicBuffer const& body_; - - public: - template - explicit - writer(message< - isRequest, basic_dynabuf_body, Fields> const& m) noexcept - : body_(m.body) - { - } - - void - init(error_code& ec) noexcept - { - beast::detail::ignore_unused(ec); - } - - std::uint64_t - content_length() const noexcept - { - return body_.size(); - } - - template - bool - write(error_code&, WriteFunction&& wf) noexcept - { - wf(body_.data()); - return true; - } - }; -}; - -} // http -} // beast - -#endif diff --git a/include/beast/http/basic_fields.hpp b/include/beast/http/basic_fields.hpp deleted file mode 100644 index 2589c827a9..0000000000 --- a/include/beast/http/basic_fields.hpp +++ /dev/null @@ -1,307 +0,0 @@ -// -// 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_HTTP_BASIC_FIELDS_HPP -#define BEAST_HTTP_BASIC_FIELDS_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -/** A container for storing HTTP header fields. - - This container is designed to store the field value pairs that make - up the fields and trailers in a HTTP message. Objects of this type - are iterable, with each element holding the field name and field - value. - - Field names are stored as-is, but comparisons are case-insensitive. - When the container is iterated, the fields are presented in the order - of insertion. For fields with the same name, the container behaves - as a `std::multiset`; there will be a separate value for each occurrence - of the field name. - - @note Meets the requirements of @b FieldSequence. -*/ -template -class basic_fields : -#if ! GENERATING_DOCS - private beast::detail::empty_base_optimization< - typename std::allocator_traits:: - template rebind_alloc< - detail::basic_fields_base::element>>, -#endif - public detail::basic_fields_base -{ - using alloc_type = typename - std::allocator_traits:: - template rebind_alloc< - detail::basic_fields_base::element>; - - using alloc_traits = - std::allocator_traits; - - using size_type = - typename std::allocator_traits::size_type; - - void - delete_all(); - - void - move_assign(basic_fields&, std::false_type); - - void - move_assign(basic_fields&, std::true_type); - - void - copy_assign(basic_fields const&, std::false_type); - - void - copy_assign(basic_fields const&, std::true_type); - - template - void - copy_from(FieldSequence const& fs) - { - for(auto const& e : fs) - insert(e.first, e.second); - } - -public: - /// The type of allocator used. - using allocator_type = Allocator; - - /** The value type of the field sequence. - - Meets the requirements of @b Field. - */ -#if GENERATING_DOCS - using value_type = implementation_defined; -#endif - - /// A const iterator to the field sequence -#if GENERATING_DOCS - using iterator = implementation_defined; -#endif - - /// A const iterator to the field sequence -#if GENERATING_DOCS - using const_iterator = implementation_defined; -#endif - - /// Default constructor. - basic_fields() = default; - - /// Destructor - ~basic_fields(); - - /** Construct the fields. - - @param alloc The allocator to use. - */ - explicit - basic_fields(Allocator const& alloc); - - /** Move constructor. - - The moved-from object becomes an empty field sequence. - - @param other The object to move from. - */ - basic_fields(basic_fields&& other); - - /** Move assignment. - - The moved-from object becomes an empty field sequence. - - @param other The object to move from. - */ - basic_fields& operator=(basic_fields&& other); - - /// Copy constructor. - basic_fields(basic_fields const&); - - /// Copy assignment. - basic_fields& operator=(basic_fields const&); - - /// Copy constructor. - template - basic_fields(basic_fields const&); - - /// Copy assignment. - template - basic_fields& operator=(basic_fields const&); - - /// Construct from a field sequence. - template - basic_fields(FwdIt first, FwdIt last); - - /// Returns `true` if the field sequence contains no elements. - bool - empty() const - { - return set_.empty(); - } - - /// Returns the number of elements in the field sequence. - std::size_t - size() const - { - return set_.size(); - } - - /// Returns a const iterator to the beginning of the field sequence. - const_iterator - begin() const - { - return list_.cbegin(); - } - - /// Returns a const iterator to the end of the field sequence. - const_iterator - end() const - { - return list_.cend(); - } - - /// Returns a const iterator to the beginning of the field sequence. - const_iterator - cbegin() const - { - return list_.cbegin(); - } - - /// Returns a const iterator to the end of the field sequence. - const_iterator - cend() const - { - return list_.cend(); - } - - /// Returns `true` if the specified field exists. - bool - exists(boost::string_ref const& name) const - { - return set_.find(name, less{}) != set_.end(); - } - - /// Returns the number of values for the specified field. - std::size_t - count(boost::string_ref const& name) const; - - /** Returns an iterator to the case-insensitive matching field name. - - If more than one field with the specified name exists, the - first field defined by insertion order is returned. - */ - iterator - find(boost::string_ref const& name) const; - - /** Returns the value for a case-insensitive matching header, or `""`. - - If more than one field with the specified name exists, the - first field defined by insertion order is returned. - */ - boost::string_ref - operator[](boost::string_ref const& name) const; - - /// Clear the contents of the basic_fields. - void - clear() noexcept; - - /** Remove a field. - - If more than one field with the specified name exists, all - matching fields will be removed. - - @param name The name of the field(s) to remove. - - @return The number of fields removed. - */ - std::size_t - erase(boost::string_ref const& name); - - /** Insert a field value. - - If a field with the same name already exists, the - existing field is untouched and a new field value pair - is inserted into the container. - - @param name The name of the field. - - @param value A string holding the value of the field. - */ - void - insert(boost::string_ref const& name, boost::string_ref value); - - /** Insert a field value. - - If a field with the same name already exists, the - existing field is untouched and a new field value pair - is inserted into the container. - - @param name The name of the field - - @param value The value of the field. The object will be - converted to a string using `boost::lexical_cast`. - */ - template - typename std::enable_if< - ! std::is_constructible::value>::type - insert(boost::string_ref name, T const& value) - { - insert(name, boost::lexical_cast(value)); - } - - /** Replace a field value. - - First removes any values with matching field names, then - inserts the new field value. - - @param name The name of the field. - - @param value A string holding the value of the field. - */ - void - replace(boost::string_ref const& name, boost::string_ref value); - - /** Replace a field value. - - First removes any values with matching field names, then - inserts the new field value. - - @param name The name of the field - - @param value The value of the field. The object will be - converted to a string using `boost::lexical_cast`. - */ - template - typename std::enable_if< - ! std::is_constructible::value>::type - replace(boost::string_ref const& name, T const& value) - { - replace(name, - boost::lexical_cast(value)); - } -}; - -} // http -} // beast - -#include - -#endif diff --git a/include/beast/http/basic_parser.hpp b/include/beast/http/basic_parser.hpp new file mode 100644 index 0000000000..16de78ef81 --- /dev/null +++ b/include/beast/http/basic_parser.hpp @@ -0,0 +1,509 @@ +// +// 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_HTTP_BASIC_PARSER_HPP +#define BEAST_HTTP_BASIC_PARSER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** A parser for decoding HTTP/1 wire format messages. + + This parser is designed to efficiently parse messages in the + HTTP/1 wire format. It allocates no memory when input is + presented as a single contiguous buffer, and uses minimal + state. It will handle chunked encoding and it understands + the semantics of the Connection, Content-Length, and Upgrade + fields. + The parser is optimized for the case where the input buffer + sequence consists of a single contiguous buffer. The + @ref beast::flat_buffer class is provided, which guarantees + that the input sequence of the stream buffer will be represented + by exactly one contiguous buffer. To ensure the optimum performance + of the parser, use @ref beast::flat_buffer with HTTP algorithms + such as @ref beast::http::read, @ref beast::http::read_some, + @ref beast::http::async_read, and @ref beast::http::async_read_some. + Alternatively, the caller may use custom techniques to ensure that + the structured portion of the HTTP message (header or chunk header) + is contained in a linear buffer. + + The interface uses CRTP (Curiously Recurring Template Pattern). + To use this class directly, derive from @ref basic_parser. When + bytes are presented, the implementation will make a series of zero + or more calls to derived class members functions (termed "callbacks" + in this context) matching a specific signature. + + Every callback must be provided by the derived class, or else + a compilation error will be generated. This exemplar shows + the signature and description of the callbacks required in + the derived class. + For each callback, the function will ensure that `!ec` is `true` + if there was no error or set to the appropriate error code if + there was one. If an error is set, the value is propagated to + the caller of the parser. + + @tparam isRequest A `bool` indicating whether the parser will be + presented with request or response message. + + @tparam Derived The derived class type. This is part of the + Curiously Recurring Template Pattern interface. + + @note If the parser encounters a field value with obs-fold + longer than 4 kilobytes in length, an error is generated. +*/ +template +class basic_parser + : private detail::basic_parser_base +{ + template + friend class basic_parser; + + // limit on the size of the stack flat buffer + static std::size_t constexpr max_stack_buffer = 8192; + + // Message will be complete after reading header + static unsigned constexpr flagSkipBody = 1<< 0; + + // Consume input buffers across semantic boundaries + static unsigned constexpr flagEager = 1<< 1; + + // The parser has read at least one byte + static unsigned constexpr flagGotSome = 1<< 2; + + // Message semantics indicate a body is expected. + // cleared if flagSkipBody set + // + static unsigned constexpr flagHasBody = 1<< 3; + + static unsigned constexpr flagHTTP11 = 1<< 4; + static unsigned constexpr flagNeedEOF = 1<< 5; + static unsigned constexpr flagExpectCRLF = 1<< 6; + static unsigned constexpr flagConnectionClose = 1<< 7; + static unsigned constexpr flagConnectionUpgrade = 1<< 8; + static unsigned constexpr flagConnectionKeepAlive = 1<< 9; + static unsigned constexpr flagContentLength = 1<< 10; + static unsigned constexpr flagChunked = 1<< 11; + static unsigned constexpr flagUpgrade = 1<< 12; + static unsigned constexpr flagFinalChunk = 1<< 13; + + static + std::uint64_t + default_body_limit(std::true_type) + { + // limit for requests + return 1 * 1024 * 1024; // 1MB + } + + static + std::uint64_t + default_body_limit(std::false_type) + { + // limit for responses + return 8 * 1024 * 1024; // 8MB + } + + std::uint64_t body_limit_; // max payload body + std::uint64_t len_; // size of chunk or body + std::unique_ptr buf_; // temp storage + std::size_t buf_len_ = 0; // size of buf_ + std::size_t skip_ = 0; // resume search here + std::uint32_t + header_limit_ = 8192; // max header size + unsigned short status_; // response status + state state_ = // initial state + state::nothing_yet; + unsigned f_ = 0; // flags + +public: + /// `true` if this parser parses requests, `false` for responses. + using is_request = + std::integral_constant; + + /// Copy constructor (disallowed) + basic_parser(basic_parser const&) = delete; + + /// Copy assignment (disallowed) + basic_parser& operator=(basic_parser const&) = delete; + + /// Destructor + ~basic_parser() = default; + + /// Default constructor + basic_parser(); + + /** Move constructor + + After the move, the only valid operation on the + moved-from object is destruction. + */ + template + basic_parser(basic_parser&&); + + /** Returns a reference to this object as a @ref basic_parser. + + This is used to pass a derived class where a base class is + expected, to choose a correct function overload when the + resolution would be ambiguous. + */ + basic_parser& + base() + { + return *this; + } + + /** Returns a constant reference to this object as a @ref basic_parser. + + This is used to pass a derived class where a base class is + expected, to choose a correct function overload when the + resolution would be ambiguous. + */ + basic_parser const& + base() const + { + return *this; + } + + /// Returns `true` if the parser has received at least one byte of input. + bool + got_some() const + { + return state_ != state::nothing_yet; + } + + /** Returns `true` if the message is complete. + + The message is complete after the full header is prduced + and one of the following is true: + + @li The skip body option was set. + + @li The semantics of the message indicate there is no body. + + @li The semantics of the message indicate a body is expected, + and the entire body was parsed. + */ + bool + is_done() const + { + return state_ == state::complete; + } + + /** Returns `true` if a the parser has produced the full header. + */ + bool + is_header_done() const + { + return state_ > state::fields; + } + + /** Returns `true` if the message is an upgrade message. + + @note The return value is undefined unless + @ref is_header_done would return `true`. + */ + bool + is_upgrade() const + { + return (f_ & flagConnectionUpgrade) != 0; + } + + /** Returns `true` if the last value for Transfer-Encoding is "chunked". + + @note The return value is undefined unless + @ref is_header_done would return `true`. + */ + bool + is_chunked() const + { + return (f_ & flagChunked) != 0; + } + + /** Returns `true` if the message has keep-alive connection semantics. + + @note The return value is undefined unless + @ref is_header_done would return `true`. + */ + bool + is_keep_alive() const; + + /** Returns the optional value of Content-Length if known. + + @note The return value is undefined unless + @ref is_header_done would return `true`. + */ + boost::optional + content_length() const; + + /** Returns `true` if the message semantics require an end of file. + + Depending on the contents of the header, the parser may + require and end of file notification to know where the end + of the body lies. If this function returns `true` it will be + necessary to call @ref put_eof when there will never be additional + data from the input. + */ + bool + need_eof() const + { + return (f_ & flagNeedEOF) != 0; + } + + /** Set the limit on the payload body. + + This function sets the maximum allowed size of the payload body, + before any encodings except chunked have been removed. Depending + on the message semantics, one of these cases will apply: + + @li The Content-Length is specified and exceeds the limit. In + this case the result @ref error::body_limit is returned + immediately after the header is parsed. + + @li The Content-Length is unspecified and the chunked encoding + is not specified as the last encoding. In this case the end of + message is determined by the end of file indicator on the + associated stream or input source. If a sufficient number of + body payload octets are presented to the parser to exceed the + configured limit, the parse fails with the result + @ref error::body_limit + + @li The Transfer-Encoding specifies the chunked encoding as the + last encoding. In this case, when the number of payload body + octets produced by removing the chunked encoding exceeds + the configured limit, the parse fails with the result + @ref error::body_limit. + + Setting the limit after any body octets have been parsed + results in undefined behavior. + + The default limit is 1MB for requests and 8MB for responses. + + @param v The payload body limit to set + */ + void + body_limit(std::uint64_t v) + { + body_limit_ = v; + } + + /** Set a limit on the total size of the header. + + This function sets the maximum allowed size of the header + including all field name, value, and delimiter characters + and also including the CRLF sequences in the serialized + input. If the end of the header is not found within the + limit of the header size, the error @ref error::header_limit + is returned by @ref put. + + Setting the limit after any header octets have been parsed + results in undefined behavior. + */ + void + header_limit(std::uint32_t v) + { + header_limit_ = v; + } + + /// Returns `true` if the eager parse option is set. + bool + eager() const + { + return (f_ & flagEager) != 0; + } + + /** Set the eager parse option. + + Normally the parser returns after successfully parsing a structured + element (header, chunk header, or chunk body) even if there are octets + remaining in the input. This is necessary when attempting to parse the + header first, or when the caller wants to inspect information which may + be invalidated by subsequent parsing, such as a chunk extension. The + `eager` option controls whether the parser keeps going after parsing + structured element if there are octets remaining in the buffer and no + error occurs. This option is automatically set or cleared during certain + stream operations to improve performance with no change in functionality. + + The default setting is `false`. + + @param v `true` to set the eager parse option or `false` to disable it. + */ + void + eager(bool v) + { + if(v) + f_ |= flagEager; + else + f_ &= ~flagEager; + } + + /// Returns `true` if the skip parse option is set. + bool + skip() + { + return (f_ & flagSkipBody) != 0; + } + + /** Set the skip parse option. + + This option controls whether or not the parser expects to see an HTTP + body, regardless of the presence or absence of certain fields such as + Content-Length or a chunked Transfer-Encoding. Depending on the request, + some responses do not carry a body. For example, a 200 response to a + CONNECT request from a tunneling proxy, or a response to a HEAD request. + In these cases, callers may use this function inform the parser that + no body is expected. The parser will consider the message complete + after the header has been received. + + @param v `true` to set the skip body option or `false` to disable it. + + @note This function must called before any bytes are processed. + */ + void + skip(bool v); + + /** Write a buffer sequence to the parser. + + This function attempts to incrementally parse the HTTP + message data stored in the caller provided buffers. Upon + success, a positive return value indicates that the parser + made forward progress, consuming that number of + bytes. + + In some cases there may be an insufficient number of octets + in the input buffer in order to make forward progress. This + is indicated by the code @ref error::need_more. When + this happens, the caller should place additional bytes into + the buffer sequence and call @ref put again. + + The error code @ref error::need_more is special. When this + error is returned, a subsequent call to @ref put may succeed + if the buffers have been updated. Otherwise, upon error + the parser may not be restarted. + + @param buffers An object meeting the requirements of + @b ConstBufferSequence that represents the next chunk of + message data. If the length of this buffer sequence is + one, the implementation will not allocate additional memory. + The class @ref flat_buffer is provided as one way to + meet this requirement + + @param ec Set to the error, if any occurred. + + @return The number of octets consumed in the buffer + sequence. The caller should remove these octets even if the + error is set. + */ + template + std::size_t + put(ConstBufferSequence const& buffers, error_code& ec); + +#if ! BEAST_DOXYGEN + std::size_t + put(boost::asio::const_buffers_1 const& buffer, + error_code& ec); +#endif + + /** Inform the parser that the end of stream was reached. + + In certain cases, HTTP needs to know where the end of + the stream is. For example, sometimes servers send + responses without Content-Length and expect the client + to consume input (for the body) until EOF. Callbacks + and errors will still be processed as usual. + + This is typically called when a read from the + underlying stream object sets the error code to + `boost::asio::error::eof`. + + @note Only valid after parsing a complete header. + + @param ec Set to the error, if any occurred. + */ + void + put_eof(error_code& ec); + +private: + inline + Derived& + impl() + { + return *static_cast(this); + } + + template + std::size_t + put_from_stack(std::size_t size, + ConstBufferSequence const& buffers, + error_code& ec); + + void + maybe_need_more( + char const* p, std::size_t n, + error_code& ec); + + void + parse_start_line( + char const*& p, char const* last, + error_code& ec, std::true_type); + + void + parse_start_line( + char const*& p, char const* last, + error_code& ec, std::false_type); + + void + parse_fields( + char const*& p, char const* last, + error_code& ec); + + void + finish_header( + error_code& ec, std::true_type); + + void + finish_header( + error_code& ec, std::false_type); + + void + parse_body(char const*& p, + std::size_t n, error_code& ec); + + void + parse_body_to_eof(char const*& p, + std::size_t n, error_code& ec); + + void + parse_chunk_header(char const*& p, + std::size_t n, error_code& ec); + + void + parse_chunk_body(char const*& p, + std::size_t n, error_code& ec); + + void + do_field(field f, + string_view value, error_code& ec); +}; + +} // http +} // beast + +#include + +#endif diff --git a/include/beast/http/basic_parser_v1.hpp b/include/beast/http/basic_parser_v1.hpp deleted file mode 100644 index cda14a0023..0000000000 --- a/include/beast/http/basic_parser_v1.hpp +++ /dev/null @@ -1,856 +0,0 @@ -// -// 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_HTTP_BASIC_PARSER_v1_HPP -#define BEAST_HTTP_BASIC_PARSER_v1_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -/** Parse flags - - The set of parser bit flags are returned by @ref basic_parser_v1::flags. -*/ -enum parse_flag -{ - chunked = 1, - connection_keep_alive = 2, - connection_close = 4, - connection_upgrade = 8, - trailing = 16, - upgrade = 32, - skipbody = 64, - contentlength = 128, - paused = 256 -}; - -/** Body maximum size option. - - Sets the maximum number of cumulative bytes allowed including - all body octets. Octets in chunk-encoded bodies are counted - after decoding. A value of zero indicates no limit on - the number of body octets. - - The default body maximum size for requests is 4MB (four - megabytes or 4,194,304 bytes) and unlimited for responses. - - @note Objects of this type are used with @ref basic_parser_v1::set_option. -*/ -struct body_max_size -{ - std::size_t value; - - explicit - body_max_size(std::size_t v) - : value(v) - { - } -}; - -/** Header maximum size option. - - Sets the maximum number of cumulative bytes allowed - including all header octets. A value of zero indicates - no limit on the number of header octets. - - The default header maximum size is 16KB (16,384 bytes). - - @note Objects of this type are used with @ref basic_parser_v1::set_option. -*/ -struct header_max_size -{ - std::size_t value; - - explicit - header_max_size(std::size_t v) - : value(v) - { - } -}; - -/** A value indicating how the parser should treat the body. - - This value is returned from the `on_header` callback in - the derived class. It controls what the parser does next - in terms of the message body. -*/ -enum class body_what -{ - /** The parser should expect a body, keep reading. - */ - normal, - - /** Skip parsing of the body. - - When returned by `on_header` this causes parsing to - complete and control to return to the caller. This - could be used when sending a response to a HEAD - request, for example. - */ - skip, - - /** The message represents an UPGRADE request. - - When returned by `on_body_prepare` this causes parsing - to complete and control to return to the caller. - */ - upgrade, - - /** Suspend parsing before reading the body. - - When returned by `on_body_prepare` this causes parsing - to pause. Control is returned to the caller, and the - parser state is preserved such that a subsequent call - to the parser will begin reading the message body. - - This could be used by callers to inspect the HTTP - header before committing to read the body. For example, - to choose the body type based on the fields. Or to - respond to an Expect: 100-continue request. - */ - pause -}; - -/// The value returned when no content length is known or applicable. -static std::uint64_t constexpr no_content_length = - (std::numeric_limits::max)(); - -/** A parser for decoding HTTP/1 wire format messages. - - This parser is designed to efficiently parse messages in the - HTTP/1 wire format. It allocates no memory and uses minimal - state. It will handle chunked encoding and it understands the - semantics of the Connection and Content-Length header fields. - - The interface uses CRTP (Curiously Recurring Template Pattern). - To use this class, derive from basic_parser. When bytes are - presented, the implementation will make a series of zero or - more calls to derived class members functions (referred to as - "callbacks" from here on) matching a specific signature. - - Every callback must be provided by the derived class, or else - a compilation error will be generated. This exemplar shows - the signature and description of the callbacks required in - the derived class. - - @code - template - struct exemplar : basic_parser_v1 - { - // Called when the first valid octet of a new message is received - // - void on_start(error_code&); - - // Called for each piece of the Request-Method - // - void on_method(boost::string_ref const&, error_code&); - - // Called for each piece of the Request-URI - // - void on_uri(boost::string_ref const&, error_code&); - - // Called for each piece of the reason-phrase - // - void on_reason(boost::string_ref const&, error_code&); - - // Called after the entire Request-Line has been parsed successfully. - // - void on_request(error_code&); - - // Called after the entire Response-Line has been parsed successfully. - // - void on_response(error_code&); - - // Called for each piece of the current header field. - // - void on_field(boost::string_ref const&, error_code&); - - // Called for each piece of the current header value. - // - void on_value(boost::string_ref const&, error_code&) - - // Called when the entire header has been parsed successfully. - // - void - on_header(std::uint64_t content_length, error_code&); - - // Called after on_header, before the body is parsed - // - body_what - on_body_what(std::uint64_t content_length, error_code&); - - // Called for each piece of the body. - // - // If the header indicates chunk encoding, the chunk - // encoding is removed from the buffer before being - // passed to the callback. - // - void on_body(boost::string_ref const&, error_code&); - - // Called when the entire message has been parsed successfully. - // At this point, @ref complete returns `true`, and the parser - // is ready to parse another message if `keep_alive` would - // return `true`. - // - void on_complete(error_code&) {} - }; - @endcode - - The return value of `on_body_what` is special, it controls - whether or not the parser should expect a body. See @ref body_what - for choices of the return value. - - If a callback sets an error, parsing stops at the current octet - and the error is returned to the caller. Callbacks must not throw - exceptions. - - @tparam isRequest A `bool` indicating whether the parser will be - presented with request or response message. - - @tparam Derived The derived class type. This is part of the - Curiously Recurring Template Pattern interface. -*/ -template -class basic_parser_v1 : public detail::parser_base -{ -private: - template - friend class basic_parser_v1; - - using self = basic_parser_v1; - typedef void(self::*pmf_t)(error_code&, boost::string_ref const&); - - enum field_state : std::uint8_t - { - h_general = 0, - h_C, - h_CO, - h_CON, - - h_matching_connection, - h_matching_proxy_connection, - h_matching_content_length, - h_matching_transfer_encoding, - h_matching_upgrade, - - h_connection, - h_content_length0, - h_content_length, - h_content_length_ows, - h_transfer_encoding, - h_upgrade, - - h_matching_transfer_encoding_chunked, - h_matching_transfer_encoding_general, - h_matching_connection_keep_alive, - h_matching_connection_close, - h_matching_connection_upgrade, - - h_transfer_encoding_chunked, - h_transfer_encoding_chunked_ows, - - h_connection_keep_alive, - h_connection_keep_alive_ows, - h_connection_close, - h_connection_close_ows, - h_connection_upgrade, - h_connection_upgrade_ows, - h_connection_token, - h_connection_token_ows - }; - - std::size_t h_max_; - std::size_t h_left_; - std::size_t b_max_; - std::size_t b_left_; - std::uint64_t content_length_; - pmf_t cb_; - state s_ : 8; - unsigned fs_ : 8; - unsigned pos_ : 8; // position in field state - unsigned http_major_ : 16; - unsigned http_minor_ : 16; - unsigned status_code_ : 16; - unsigned flags_ : 9; - bool upgrade_ : 1; // true if parser exited for upgrade - -public: - /// Default constructor - basic_parser_v1(); - - /// Copy constructor. - template - basic_parser_v1(basic_parser_v1< - isRequest, OtherDerived> const& other); - - /// Copy assignment. - template - basic_parser_v1& operator=(basic_parser_v1< - isRequest, OtherDerived> const& other); - - /** Set options on the parser. - - @param args One or more parser options to set. - */ -#if GENERATING_DOCS - template - void - set_option(Args&&... args) -#else - template - void - set_option(A1&& a1, A2&& a2, An&&... an) -#endif - { - set_option(std::forward(a1)); - set_option(std::forward(a2), - std::forward(an)...); - } - - /// Set the header maximum size option - void - set_option(header_max_size const& o) - { - h_max_ = o.value; - h_left_ = h_max_; - } - - /// Set the body maximum size option - void - set_option(body_max_size const& o) - { - b_max_ = o.value; - b_left_ = b_max_; - } - - /// Returns internal flags associated with the parser. - unsigned - flags() const - { - return flags_; - } - - /** Returns `true` if the message end is indicated by eof. - - This function returns true if the semantics of the message require - that the end of the message is signaled by an end of file. For - example, if the message is a HTTP/1.0 message and the Content-Length - is unspecified, the end of the message is indicated by an end of file. - - @return `true` if write_eof must be used to indicate the message end. - */ - bool - needs_eof() const - { - return needs_eof( - std::integral_constant{}); - } - - /** Returns the major HTTP version number. - - Examples: - * Returns 1 for HTTP/1.1 - * Returns 1 for HTTP/1.0 - - @return The HTTP major version number. - */ - unsigned - http_major() const - { - return http_major_; - } - - /** Returns the minor HTTP version number. - - Examples: - * Returns 1 for HTTP/1.1 - * Returns 0 for HTTP/1.0 - - @return The HTTP minor version number. - */ - unsigned - http_minor() const - { - return http_minor_; - } - - /** Returns `true` if the message is an upgrade message. - - A value of `true` indicates that the parser has successfully - completed parsing a HTTP upgrade message. - - @return `true` if the message is an upgrade message. - */ - bool - upgrade() const - { - return upgrade_; - } - - /** Returns the numeric HTTP Status-Code of a response. - - @return The Status-Code. - */ - unsigned - status_code() const - { - return status_code_; - } - - /** Returns `true` if the connection should be kept open. - - @note This function is only valid to call when the parser - is complete. - */ - bool - keep_alive() const; - - /** Returns `true` if the parse has completed succesfully. - - When the parse has completed successfully, and the semantics - of the parsed message indicate that the connection is still - active, a subsequent call to `write` will begin parsing a - new message. - - @return `true` If the parsing has completed successfully. - */ - bool - complete() const - { - return - s_ == s_restart || - s_ == s_closed_complete || - (flags_ & parse_flag::paused); - } - - /** Write a sequence of buffers to the parser. - - @param buffers An object meeting the requirements of - ConstBufferSequence that represents the input sequence. - - @param ec Set to the error, if any error occurred. - - @return The number of bytes consumed in the input sequence. - */ - template -#if GENERATING_DOCS - std::size_t -#else - typename std::enable_if< - ! std::is_convertible::value, - std::size_t>::type -#endif - write(ConstBufferSequence const& buffers, error_code& ec); - - /** Write a single buffer of data to the parser. - - @param buffer The buffer to write. - @param ec Set to the error, if any error occurred. - - @return The number of bytes consumed in the buffer. - */ - std::size_t - write(boost::asio::const_buffer const& buffer, error_code& ec); - - /** Called to indicate the end of file. - - HTTP needs to know where the end of the stream is. For example, - sometimes servers send responses without Content-Length and - expect the client to consume input (for the body) until EOF. - Callbacks and errors will still be processed as usual. - - @note This is typically called when a socket read returns eof. - */ - void - write_eof(error_code& ec); - -protected: - /** Reset the parsing state. - - The state of the parser is reset to expect the beginning of - a new request or response. The old state is discarded. - */ - void - reset(); - -private: - Derived& - impl() - { - return *static_cast(this); - } - - void - reset(std::true_type) - { - s_ = s_req_start; - } - - void - reset(std::false_type) - { - s_ = s_res_start; - } - - void - init(std::true_type) - { - // Request: 16KB max header, 4MB max body - h_max_ = 16 * 1024; - b_max_ = 4 * 1024 * 1024; - } - - void - init(std::false_type) - { - // Response: 16KB max header, unlimited body - h_max_ = 16 * 1024; - b_max_ = 0; - } - - void - init() - { - init(std::integral_constant{}); - reset(); - } - - bool - needs_eof(std::true_type) const; - - bool - needs_eof(std::false_type) const; - - template> - struct check_on_start : std::false_type {}; - - template - struct check_on_start().on_start( - std::declval()) - )>> : std::true_type { }; - - template> - struct check_on_method : std::false_type {}; - - template - struct check_on_method().on_method( - std::declval(), - std::declval()) - )>> : std::true_type {}; - - template> - struct check_on_uri : std::false_type {}; - - template - struct check_on_uri().on_uri( - std::declval(), - std::declval()) - )>> : std::true_type {}; - - template> - struct check_on_reason : std::false_type {}; - - template - struct check_on_reason().on_reason( - std::declval(), - std::declval()) - )>> : std::true_type {}; - - template> - struct check_on_request : std::false_type {}; - - template - struct check_on_request().on_request( - std::declval()) - )>> : std::true_type {}; - - template> - struct check_on_response : std::false_type {}; - - template - struct check_on_response().on_response( - std::declval()) - )>> : std::true_type {}; - - template> - struct check_on_field : std::false_type {}; - - template - struct check_on_field().on_field( - std::declval(), - std::declval()) - )>> : std::true_type {}; - - template> - struct check_on_value : std::false_type {}; - - template - struct check_on_value().on_value( - std::declval(), - std::declval()) - )>> : std::true_type {}; - - template> - struct check_on_headers : std::false_type {}; - - template - struct check_on_headers().on_header( - std::declval(), - std::declval()) - )>> : std::true_type {}; - - // VFALCO Can we use std::is_detected? Is C++11 capable? - template - class check_on_body_what_t - { - template().on_body_what( - std::declval(), - std::declval())), - body_what>> - static R check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); - public: - static bool const value = type::value; - }; - template - using check_on_body_what = - std::integral_constant::value>; - - template> - struct check_on_body : std::false_type {}; - - template - struct check_on_body().on_body( - std::declval(), - std::declval()) - )>> : std::true_type {}; - - template> - struct check_on_complete : std::false_type {}; - - template - struct check_on_complete().on_complete( - std::declval()) - )>> : std::true_type {}; - - void call_on_start(error_code& ec) - { - static_assert(check_on_start::value, - "on_start requirements not met"); - impl().on_start(ec); - } - - void call_on_method(error_code& ec, - boost::string_ref const& s, std::true_type) - { - static_assert(check_on_method::value, - "on_method requirements not met"); - if(h_max_ && s.size() > h_left_) - { - ec = parse_error::header_too_big; - return; - } - h_left_ -= s.size(); - impl().on_method(s, ec); - } - - void call_on_method(error_code&, - boost::string_ref const&, std::false_type) - { - } - - void call_on_method(error_code& ec, - boost::string_ref const& s) - { - call_on_method(ec, s, - std::integral_constant{}); - } - - void call_on_uri(error_code& ec, - boost::string_ref const& s, std::true_type) - { - static_assert(check_on_uri::value, - "on_uri requirements not met"); - if(h_max_ && s.size() > h_left_) - { - ec = parse_error::header_too_big; - return; - } - h_left_ -= s.size(); - impl().on_uri(s, ec); - } - - void call_on_uri(error_code&, - boost::string_ref const&, std::false_type) - { - } - - void call_on_uri(error_code& ec, - boost::string_ref const& s) - { - call_on_uri(ec, s, - std::integral_constant{}); - } - - void call_on_reason(error_code& ec, - boost::string_ref const& s, std::true_type) - { - static_assert(check_on_reason::value, - "on_reason requirements not met"); - if(h_max_ && s.size() > h_left_) - { - ec = parse_error::header_too_big; - return; - } - h_left_ -= s.size(); - impl().on_reason(s, ec); - } - - void call_on_reason(error_code&, - boost::string_ref const&, std::false_type) - { - } - - void call_on_reason(error_code& ec, boost::string_ref const& s) - { - call_on_reason(ec, s, - std::integral_constant{}); - } - - void call_on_request(error_code& ec, std::true_type) - { - static_assert(check_on_request::value, - "on_request requirements not met"); - impl().on_request(ec); - } - - void call_on_request(error_code&, std::false_type) - { - } - - void call_on_request(error_code& ec) - { - call_on_request(ec, - std::integral_constant{}); - } - - void call_on_response(error_code& ec, std::true_type) - { - static_assert(check_on_response::value, - "on_response requirements not met"); - impl().on_response(ec); - } - - void call_on_response(error_code&, std::false_type) - { - } - - void call_on_response(error_code& ec) - { - call_on_response(ec, - std::integral_constant{}); - } - - void call_on_field(error_code& ec, - boost::string_ref const& s) - { - static_assert(check_on_field::value, - "on_field requirements not met"); - if(h_max_ && s.size() > h_left_) - { - ec = parse_error::header_too_big; - return; - } - h_left_ -= s.size(); - impl().on_field(s, ec); - } - - void call_on_value(error_code& ec, - boost::string_ref const& s) - { - static_assert(check_on_value::value, - "on_value requirements not met"); - if(h_max_ && s.size() > h_left_) - { - ec = parse_error::header_too_big; - return; - } - h_left_ -= s.size(); - impl().on_value(s, ec); - } - - void - call_on_headers(error_code& ec) - { - static_assert(check_on_headers::value, - "on_header requirements not met"); - impl().on_header(content_length_, ec); - } - - body_what - call_on_body_what(error_code& ec) - { - static_assert(check_on_body_what::value, - "on_body_what requirements not met"); - return impl().on_body_what(content_length_, ec); - } - - void call_on_body(error_code& ec, - boost::string_ref const& s) - { - static_assert(check_on_body::value, - "on_body requirements not met"); - if(b_max_ && s.size() > b_left_) - { - ec = parse_error::body_too_big; - return; - } - b_left_ -= s.size(); - impl().on_body(s, ec); - } - - void call_on_complete(error_code& ec) - { - static_assert(check_on_complete::value, - "on_complete requirements not met"); - impl().on_complete(ec); - } -}; - -} // http -} // beast - -#include - -#endif diff --git a/include/beast/http/buffer_body.hpp b/include/beast/http/buffer_body.hpp new file mode 100644 index 0000000000..61a99f178f --- /dev/null +++ b/include/beast/http/buffer_body.hpp @@ -0,0 +1,224 @@ +// +// 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_HTTP_BUFFER_BODY_HPP +#define BEAST_HTTP_BUFFER_BODY_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** A @b Body using a caller provided buffer + + Messages using this body type may be serialized and parsed. + To use this class, the caller must initialize the members + of @ref buffer_body::value_type to appropriate values before + each call to read or write during a stream operation. +*/ +struct buffer_body +{ + /// The type of the body member when used in a message. + struct value_type + { + /** A pointer to a contiguous area of memory of @ref size octets, else `nullptr`. + + @par When Serializing + + If this is `nullptr` and `more` is `true`, the error + @ref error::need_buffer will be returned from @ref serializer::get + Otherwise, the serializer will use the memory pointed to + by `data` having `size` octets of valid storage as the + next buffer representing the body. + + @par When Parsing + + If this is `nullptr`, the error @ref error::need_buffer + will be returned from @ref parser::put. Otherwise, the + parser will store body octets into the memory pointed to + by `data` having `size` octets of valid storage. After + octets are stored, the `data` and `size` members are + adjusted: `data` is incremented to point to the next + octet after the data written, while `size` is decremented + to reflect the remaining space at the memory location + pointed to by `data`. + */ + void* data = nullptr; + + /** The number of octets in the buffer pointed to by @ref data. + + @par When Serializing + + If `data` is `nullptr` during serialization, this value + is ignored. Otherwise, it represents the number of valid + body octets pointed to by `data`. + + @par When Parsing + + The value of this field will be decremented during parsing + to indicate the number of remaining free octets in the + buffer pointed to by `data`. When it reaches zero, the + parser will return @ref error::need_buffer, indicating to + the caller that the values of `data` and `size` should be + updated to point to a new memory buffer. + */ + std::size_t size = 0; + + /** `true` if this is not the last buffer. + + @par When Serializing + + If this is `true` and `data` is `nullptr`, the error + @ref error::need_buffer will be returned from @ref serializer::get + + @par When Parsing + + This field is not used during parsing. + */ + bool more = true; + }; + + /** The algorithm for serializing the body + + Meets the requirements of @b BodyReader. + */ +#if BEAST_DOXYGEN + using reader = implementation_defined; +#else + class reader + { + bool toggle_ = false; + value_type const& body_; + + public: + using const_buffers_type = + boost::asio::const_buffers_1; + + template + explicit + reader(message const& msg) + : body_(msg.body) + { + } + + void + init(error_code& ec) + { + ec.assign(0, ec.category()); + } + + boost::optional< + std::pair> + get(error_code& ec) + { + if(toggle_) + { + if(body_.more) + { + toggle_ = false; + ec = error::need_buffer; + } + else + { + ec.assign(0, ec.category()); + } + return boost::none; + } + if(body_.data) + { + ec.assign(0, ec.category()); + toggle_ = true; + return {{const_buffers_type{ + body_.data, body_.size}, body_.more}}; + } + if(body_.more) + ec = error::need_buffer; + else + ec.assign(0, ec.category()); + return boost::none; + } + }; +#endif + + /** The algorithm for parsing the body + + Meets the requirements of @b BodyReader. + */ +#if BEAST_DOXYGEN + using writer = implementation_defined; +#else + class writer + { + value_type& body_; + + public: + template + explicit + writer(message& m) + : body_(m.body) + { + } + + void + init(boost::optional const&, error_code& ec) + { + ec.assign(0, ec.category()); + } + + template + std::size_t + put(ConstBufferSequence const& buffers, + error_code& ec) + { + using boost::asio::buffer_size; + using boost::asio::buffer_copy; + if(! body_.data) + { + ec = error::need_buffer; + return 0; + } + auto const bytes_transferred = + buffer_copy(boost::asio::buffer( + body_.data, body_.size), buffers); + body_.data = reinterpret_cast( + body_.data) + bytes_transferred; + body_.size -= bytes_transferred; + if(bytes_transferred == buffer_size(buffers)) + ec.assign(0, ec.category()); + else + ec = error::need_buffer; + return bytes_transferred; + } + + void + finish(error_code& ec) + { + ec.assign(0, ec.category()); + } + }; +#endif +}; + +#if ! BEAST_DOXYGEN +// operator<< is not supported for buffer_body +template +std::ostream& +operator<<(std::ostream& os, message const& msg) = delete; +#endif + +} // http +} // beast + +#endif diff --git a/include/beast/http/chunk_encode.hpp b/include/beast/http/chunk_encode.hpp deleted file mode 100644 index a437fe1839..0000000000 --- a/include/beast/http/chunk_encode.hpp +++ /dev/null @@ -1,75 +0,0 @@ -// -// 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_HTTP_CHUNK_ENCODE_HPP -#define BEAST_HTTP_CHUNK_ENCODE_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -/** Returns a chunk-encoded ConstBufferSequence. - - This returns a buffer sequence representing the - first chunk of a chunked transfer coded body. - - @param fin `true` if this is the last chunk. - - @param buffers The input buffer sequence. - - @return A chunk-encoded ConstBufferSequence representing the input. - - @see rfc7230 section 4.1.3 -*/ -template -#if GENERATING_DOCS -implementation_defined -#else -beast::detail::buffer_cat_helper< - detail::chunk_encode_delim, - ConstBufferSequence, - boost::asio::const_buffers_1> -#endif -chunk_encode(bool fin, ConstBufferSequence const& buffers) -{ - using boost::asio::buffer_size; - return buffer_cat( - detail::chunk_encode_delim{buffer_size(buffers)}, - buffers, - fin ? boost::asio::const_buffers_1{"\r\n0\r\n\r\n", 7} - : boost::asio::const_buffers_1{"\r\n", 2}); -} - -/** Returns a chunked encoding final chunk. - - @see rfc7230 section 4.1.3 -*/ -inline -#if GENERATING_DOCS -implementation_defined -#else -boost::asio::const_buffers_1 -#endif -chunk_encode_final() -{ - return boost::asio::const_buffers_1{"0\r\n\r\n", 5}; -} - -} // http -} // beast - -#endif diff --git a/include/beast/http/concepts.hpp b/include/beast/http/concepts.hpp deleted file mode 100644 index 2e83fe7a48..0000000000 --- a/include/beast/http/concepts.hpp +++ /dev/null @@ -1,231 +0,0 @@ -// -// 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_HTTP_TYPE_CHECK_HPP -#define BEAST_HTTP_TYPE_CHECK_HPP - -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -namespace detail { - -struct write_function -{ - template - void - operator()(ConstBufferSequence const&); -}; - -template> -struct has_value_type : std::false_type {}; - -template -struct has_value_type > : std::true_type {}; - -template> -struct has_content_length : std::false_type {}; - -template -struct has_content_length().content_length() - )> > : std::true_type -{ - static_assert(std::is_convertible< - decltype(std::declval().content_length()), - std::uint64_t>::value, - "Writer::content_length requirements not met"); -}; - -template -class is_Writer -{ - template().init(std::declval()), - std::true_type{})> - static R check1(int); - template - static std::false_type check1(...); - using type1 = decltype(check1(0)); - - // VFALCO This is unfortunate, we have to provide the template - // argument type because this is not a deduced context? - // - template().template write( - std::declval(), - std::declval())) - , bool>> - static R check2(int); - template - static std::false_type check2(...); - using type2 = decltype(check2(0)); - -public: - static_assert(std::is_same< - typename M::body_type::writer, T>::value, - "Mismatched writer and message"); - - using type = std::integral_constant::value - && type1::value - && type2::value - >; -}; - -template -class is_Parser -{ - template().complete()), - bool>> - static R check1(int); - template - static std::false_type check1(...); - using type1 = decltype(check1(0)); - - template().write( - std::declval(), - std::declval())), - std::size_t>> - static R check2(int); - template - static std::false_type check2(...); - using type2 = decltype(check2(0)); - - template().write_eof(std::declval()), - std::true_type{})> - static R check3(int); - template - static std::false_type check3(...); - using type3 = decltype(check3(0)); - -public: - using type = std::integral_constant; -}; - -} // detail - -/// Determine if `T` meets the requirements of @b Body. -template -#if GENERATING_DOCS -struct is_Body : std::integral_constant{}; -#else -using is_Body = detail::has_value_type; -#endif - -/** Determine if a @b Body has a nested type `reader`. - - @tparam T The type to check, which must meet the - requirements of @b Body. -*/ -#if GENERATING_DOCS -template -struct has_reader : std::integral_constant{}; -#else -template> -struct has_reader : std::false_type {}; - -template -struct has_reader > : std::true_type {}; -#endif - -/** Determine if a @b Body has a nested type `writer`. - - @tparam T The type to check, which must meet the - requirements of @b Body. -*/ -#if GENERATING_DOCS -template -struct has_writer : std::integral_constant{}; -#else -template> -struct has_writer : std::false_type {}; - -template -struct has_writer > : std::true_type {}; -#endif - -/** Determine if `T` meets the requirements of @b Reader for `M`. - - @tparam T The type to test. - - @tparam M The message type to test with, which must be of - type `message`. -*/ -#if GENERATING_DOCS -template -struct is_Reader : std::integral_constant {}; -#else -template> -struct is_Reader : std::false_type {}; - -template -struct is_Reader().init( - std::declval()), - std::declval().write( - std::declval(), - std::declval(), - std::declval()) - )> > : std::integral_constant::value - > -{ - static_assert(std::is_same< - typename M::body_type::reader, T>::value, - "Mismatched reader and message"); -}; -#endif - -/** Determine if `T` meets the requirements of @b Writer for `M`. - - @tparam T The type to test. - - @tparam M The message type to test with, which must be of - type `message`. -*/ -template -#if GENERATING_DOCS -struct is_Writer : std::integral_constant {}; -#else -using is_Writer = typename detail::is_Writer::type; -#endif - -/// Determine if `T` meets the requirements of @b Parser. -template -#if GENERATING_DOCS -struct is_Parser : std::integral_constant{}; -#else -using is_Parser = typename detail::is_Parser::type; -#endif - -} // http -} // beast - -#endif diff --git a/include/beast/http/detail/basic_fields.hpp b/include/beast/http/detail/basic_fields.hpp deleted file mode 100644 index 1b296df3b6..0000000000 --- a/include/beast/http/detail/basic_fields.hpp +++ /dev/null @@ -1,214 +0,0 @@ -// -// 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_HTTP_DETAIL_BASIC_FIELDS_HPP -#define BEAST_HTTP_DETAIL_BASIC_FIELDS_HPP - -#include -#include -#include -#include - -namespace beast { -namespace http { - -template -class basic_fields; - -namespace detail { - -class basic_fields_base -{ -public: - struct value_type - { - std::string first; - std::string second; - - value_type(boost::string_ref const& name_, - boost::string_ref const& value_) - : first(name_) - , second(value_) - { - } - - boost::string_ref - name() const - { - return first; - } - - boost::string_ref - value() const - { - return second; - } - }; - -protected: - template - friend class beast::http::basic_fields; - - struct element - : boost::intrusive::set_base_hook < - boost::intrusive::link_mode < - boost::intrusive::normal_link>> - , boost::intrusive::list_base_hook < - boost::intrusive::link_mode < - boost::intrusive::normal_link>> - { - value_type data; - - element(boost::string_ref const& name, - boost::string_ref const& value) - : data(name, value) - { - } - }; - - struct less : private beast::detail::ci_less - { - template - bool - operator()(String const& lhs, element const& rhs) const - { - return ci_less::operator()(lhs, rhs.data.first); - } - - template - bool - operator()(element const& lhs, String const& rhs) const - { - return ci_less::operator()(lhs.data.first, rhs); - } - - bool - operator()(element const& lhs, element const& rhs) const - { - return ci_less::operator()( - lhs.data.first, rhs.data.first); - } - }; - - using list_t = boost::intrusive::make_list>::type; - - using set_t = boost::intrusive::make_multiset, - boost::intrusive::compare>::type; - - // data - set_t set_; - list_t list_; - - basic_fields_base(set_t&& set, list_t&& list) - : set_(std::move(set)) - , list_(std::move(list)) - { - } - -public: - class const_iterator; - - using iterator = const_iterator; - - basic_fields_base() = default; -}; - -//------------------------------------------------------------------------------ - -class basic_fields_base::const_iterator -{ - using iter_type = list_t::const_iterator; - - iter_type it_; - - template - friend class beast::http::basic_fields; - - friend class basic_fields_base; - - const_iterator(iter_type it) - : it_(it) - { - } - -public: - using value_type = - typename basic_fields_base::value_type; - using pointer = value_type const*; - using reference = value_type const&; - using difference_type = std::ptrdiff_t; - using iterator_category = - std::bidirectional_iterator_tag; - - const_iterator() = default; - const_iterator(const_iterator&& other) = default; - const_iterator(const_iterator const& other) = default; - const_iterator& operator=(const_iterator&& other) = default; - const_iterator& operator=(const_iterator const& other) = default; - - bool - operator==(const_iterator const& other) const - { - return it_ == other.it_; - } - - bool - operator!=(const_iterator const& other) const - { - return !(*this == other); - } - - reference - operator*() const - { - return it_->data; - } - - pointer - operator->() const - { - return &**this; - } - - const_iterator& - operator++() - { - ++it_; - return *this; - } - - const_iterator - operator++(int) - { - auto temp = *this; - ++(*this); - return temp; - } - - const_iterator& - operator--() - { - --it_; - return *this; - } - - const_iterator - operator--(int) - { - auto temp = *this; - --(*this); - return temp; - } -}; - -} // detail -} // http -} // beast - -#endif diff --git a/include/beast/http/detail/basic_parsed_list.hpp b/include/beast/http/detail/basic_parsed_list.hpp new file mode 100644 index 0000000000..98f9acae17 --- /dev/null +++ b/include/beast/http/detail/basic_parsed_list.hpp @@ -0,0 +1,194 @@ +// +// 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_HTTP_DETAIL_BASIC_PARSED_LIST_HPP +#define BEAST_HTTP_DETAIL_BASIC_PARSED_LIST_HPP + +#include +#include +#include +#include + +namespace beast { +namespace http { +namespace detail { + +/** A list parser which presents the sequence as a container. +*/ +template +class basic_parsed_list +{ + string_view s_; + +public: + /// The type of policy this list uses for parsing. + using policy_type = Policy; + + /// The type of each element in the list. + using value_type = typename Policy::value_type; + + /// A constant iterator to a list element. +#if BEAST_DOXYGEN + using const_iterator = implementation_defined; +#else + class const_iterator; +#endif + + class const_iterator + : private beast::detail:: + empty_base_optimization + { + basic_parsed_list const* list_ = nullptr; + char const* it_ = nullptr; + typename Policy::value_type v_; + bool error_ = false; + + public: + using value_type = + typename Policy::value_type; + using reference = value_type const&; + using pointer = value_type const*; + using difference_type = std::ptrdiff_t; + using iterator_category = + std::forward_iterator_tag; + + const_iterator() = default; + + bool + operator==( + const_iterator const& other) const + { + return + other.list_ == list_ && + other.it_ == it_; + } + + bool + operator!=( + const_iterator const& other) const + { + return ! (*this == other); + } + + reference + operator*() const + { + return v_; + } + + const_iterator& + operator++() + { + increment(); + return *this; + } + + const_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + + bool + error() const + { + return error_; + } + + private: + friend class basic_parsed_list; + + const_iterator( + basic_parsed_list const& list, bool at_end) + : list_(&list) + , it_(at_end ? nullptr : + list.s_.begin()) + { + if(! at_end) + increment(); + } + + void + increment() + { + if(! this->member()( + v_, it_, list_->s_)) + { + it_ = nullptr; + error_ = true; + } + } + }; + + /// Construct a list from a string + explicit + basic_parsed_list(string_view s) + : s_(s) + { + } + + /// Return a const iterator to the beginning of the list + const_iterator begin() const; + + /// Return a const iterator to the end of the list + const_iterator end() const; + + /// Return a const iterator to the beginning of the list + const_iterator cbegin() const; + + /// Return a const iterator to the end of the list + const_iterator cend() const; +}; + +template +inline +auto +basic_parsed_list:: +begin() const -> + const_iterator +{ + return const_iterator{*this, false}; +} + +template +inline +auto +basic_parsed_list:: +end() const -> + const_iterator +{ + return const_iterator{*this, true}; +} + +template +inline +auto +basic_parsed_list:: +cbegin() const -> + const_iterator +{ + return const_iterator{*this, false}; +} + +template +inline +auto +basic_parsed_list:: +cend() const -> + const_iterator +{ + return const_iterator{*this, true}; +} + +} // detail +} // http +} // beast + +#endif + diff --git a/include/beast/http/detail/basic_parser.hpp b/include/beast/http/detail/basic_parser.hpp new file mode 100644 index 0000000000..d61a32dc93 --- /dev/null +++ b/include/beast/http/detail/basic_parser.hpp @@ -0,0 +1,718 @@ +// +// 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_HTTP_DETAIL_BASIC_PARSER_HPP +#define BEAST_HTTP_DETAIL_BASIC_PARSER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { +namespace detail { + +class basic_parser_base +{ +protected: + // limit on the size of the obs-fold buffer + // + // https://stackoverflow.com/questions/686217/maximum-on-http-header-values + // + static std::size_t constexpr max_obs_fold = 4096; + + enum class state + { + nothing_yet = 0, + start_line, + fields, + body0, + body, + body_to_eof0, + body_to_eof, + chunk_header0, + chunk_header, + chunk_body, + complete + }; + + static + bool + is_pathchar(char c) + { + // VFALCO This looks the same as the one below... + + // TEXT = + static bool constexpr tab[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 32 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 48 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 112 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 128 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 144 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 160 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 176 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 192 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 208 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 224 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240 + }; + return tab[static_cast(c)]; + } + + static + inline + bool + unhex(unsigned char& d, char c) + { + static signed char constexpr tab[256] = { + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0 + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 16 + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 32 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1, // 48 + -1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 64 + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 80 + -1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 96 + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 112 + + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 128 + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 144 + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 160 + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 176 + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 192 + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 208 + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 224 + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 240 + }; + d = static_cast( + tab[static_cast(c)]); + return d != static_cast(-1); + } + + static + bool + is_digit(char c) + { + return static_cast(c-'0') < 10; + } + + static + bool + is_print(char c) + { + return static_cast(c-32) < 95; + } + + template + static + FwdIt + trim_front(FwdIt it, FwdIt const& end) + { + while(it != end) + { + if(*it != ' ' && *it != '\t') + break; + ++it; + } + return it; + } + + template + static + RanIt + trim_back( + RanIt it, RanIt const& first) + { + while(it != first) + { + auto const c = it[-1]; + if(c != ' ' && c != '\t') + break; + --it; + } + return it; + } + + static + string_view + make_string(char const* first, char const* last) + { + return {first, static_cast< + std::size_t>(last - first)}; + } + + //-------------------------------------------------------------------------- + + std::pair + find_fast( + char const* buf, + char const* buf_end, + char const* ranges, + size_t ranges_size) + { + bool found = false; + boost::ignore_unused(buf_end, ranges, ranges_size); + return {buf, found}; + } + + // VFALCO Can SIMD help this? + static + char const* + find_eol( + char const* it, char const* last, + error_code& ec) + { + for(;;) + { + if(it == last) + { + ec.assign(0, ec.category()); + return nullptr; + } + if(*it == '\r') + { + if(++it == last) + { + ec.assign(0, ec.category()); + return nullptr; + } + if(*it != '\n') + { + ec = error::bad_line_ending; + return nullptr; + } + ec.assign(0, ec.category()); + return ++it; + } + // VFALCO Should we handle the legacy case + // for lines terminated with a single '\n'? + ++it; + } + } + + // VFALCO Can SIMD help this? + static + char const* + find_eom(char const* p, char const* last) + { + for(;;) + { + if(p + 4 > last) + return nullptr; + if(p[3] != '\n') + { + if(p[3] == '\r') + ++p; + else + p += 4; + } + else if(p[2] != '\r') + { + p += 4; + } + else if(p[1] != '\n') + { + p += 2; + } + else if(p[0] != '\r') + { + p += 2; + } + else + { + return p + 4; + } + } + } + + //-------------------------------------------------------------------------- + + char const* + parse_token_to_eol( + char const* p, + char const* last, + char const*& token_last, + error_code& ec) + { + for(;; ++p) + { + if(p >= last) + { + ec = error::need_more; + return p; + } + if(BOOST_UNLIKELY(! is_print(*p))) + if((BOOST_LIKELY(static_cast< + unsigned char>(*p) < '\040') && + BOOST_LIKELY(*p != '\011')) || + BOOST_UNLIKELY(*p == '\177')) + goto found_control; + } + found_control: + if(BOOST_LIKELY(*p == '\r')) + { + if(++p >= last) + { + ec = error::need_more; + return last; + } + if(*p++ != '\n') + { + ec = error::bad_line_ending; + return last; + } + token_last = p - 2; + } + #if 0 + // VFALCO This allows `\n` by itself + // to terminate a line + else if(*p == '\n') + { + token_last = p; + ++p; + } + #endif + else + { + // invalid character + return nullptr; + } + return p; + } + + template + static + bool + parse_dec(Iter it, Iter last, Unsigned& v) + { + if(! is_digit(*it)) + return false; + v = *it - '0'; + for(;;) + { + if(! is_digit(*++it)) + break; + auto const d = *it - '0'; + if(v > ((std::numeric_limits< + Unsigned>::max)() - 10) / 10) + return false; + v = 10 * v + d; + } + return it == last; + } + + template + bool + parse_hex(Iter& it, Unsigned& v) + { + unsigned char d; + if(! unhex(d, *it)) + return false; + v = d; + for(;;) + { + if(! unhex(d, *++it)) + break; + auto const v0 = v; + v = 16 * v + d; + if(v < v0) + return false; + } + return true; + } + + static + bool + parse_crlf(char const*& it) + { + if( it[0] != '\r' || it[1] != '\n') + return false; + it += 2; + return true; + } + + static + void + parse_method( + char const*& it, char const* last, + string_view& result, error_code& ec) + { + // parse token SP + auto const first = it; + for(;; ++it) + { + if(it + 1 > last) + { + ec = error::need_more; + return; + } + if(! detail::is_tchar(*it)) + break; + } + if(it + 1 > last) + { + ec = error::need_more; + return; + } + if(*it != ' ') + { + ec = error::bad_method; + return; + } + if(it == first) + { + // cannot be empty + ec = error::bad_method; + return; + } + result = make_string(first, it++); + } + + static + void + parse_target( + char const*& it, char const* last, + string_view& result, error_code& ec) + { + // parse target SP + auto const first = it; + for(;; ++it) + { + if(it + 1 > last) + { + ec = error::need_more; + return; + } + if(! is_pathchar(*it)) + break; + } + if(it + 1 > last) + { + ec = error::need_more; + return; + } + if(*it != ' ') + { + ec = error::bad_target; + return; + } + if(it == first) + { + // cannot be empty + ec = error::bad_target; + return; + } + result = make_string(first, it++); + } + + static + void + parse_version( + char const*& it, char const* last, + int& result, error_code& ec) + { + if(it + 8 > last) + { + ec = error::need_more; + return; + } + if(*it++ != 'H') + { + ec = error::bad_version; + return; + } + if(*it++ != 'T') + { + ec = error::bad_version; + return; + } + if(*it++ != 'T') + { + ec = error::bad_version; + return; + } + if(*it++ != 'P') + { + ec = error::bad_version; + return; + } + if(*it++ != '/') + { + ec = error::bad_version; + return; + } + if(! is_digit(*it)) + { + ec = error::bad_version; + return; + } + result = 10 * (*it++ - '0'); + if(*it++ != '.') + { + ec = error::bad_version; + return; + } + if(! is_digit(*it)) + { + ec = error::bad_version; + return; + } + result += *it++ - '0'; + } + + static + void + parse_status( + char const*& it, char const* last, + unsigned short& result, error_code& ec) + { + // parse 3(digit) SP + if(it + 4 > last) + { + ec = error::need_more; + return; + } + if(! is_digit(*it)) + { + ec = error::bad_status; + return; + } + result = 100 * (*it++ - '0'); + if(! is_digit(*it)) + { + ec = error::bad_status; + return; + } + result += 10 * (*it++ - '0'); + if(! is_digit(*it)) + { + ec = error::bad_status; + return; + } + result += *it++ - '0'; + if(*it++ != ' ') + { + ec = error::bad_status; + return; + } + } + + void + parse_reason( + char const*& it, char const* last, + string_view& result, error_code& ec) + { + auto const first = it; + char const* token_last = nullptr; + auto p = parse_token_to_eol( + it, last, token_last, ec); + if(ec) + return; + if(! p) + { + ec = error::bad_reason; + return; + } + result = make_string(first, token_last); + it = p; + } + + template + void + parse_field( + char const*& p, + char const* last, + string_view& name, + string_view& value, + static_string& buf, + error_code& ec) + { + /* header-field = field-name ":" OWS field-value OWS + + field-name = token + field-value = *( field-content / obs-fold ) + field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] + field-vchar = VCHAR / obs-text + + obs-fold = CRLF 1*( SP / HTAB ) + ; obsolete line folding + ; see Section 3.2.4 + + token = 1* + CHAR = + sep = "(" | ")" | "<" | ">" | "@" + | "," | ";" | ":" | "\" | <"> + | "/" | "[" | "]" | "?" | "=" + | "{" | "}" | SP | HT + */ + static char const* is_token = + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0" + "\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1" + "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; + + // name + BOOST_ALIGNMENT(16) static const char ranges1[] = + "\x00 " /* control chars and up to SP */ + "\"\"" /* 0x22 */ + "()" /* 0x28,0x29 */ + ",," /* 0x2c */ + "//" /* 0x2f */ + ":@" /* 0x3a-0x40 */ + "[]" /* 0x5b-0x5d */ + "{\377"; /* 0x7b-0xff */ + auto first = p; + bool found; + std::tie(p, found) = find_fast( + p, last, ranges1, sizeof(ranges1)-1); + if(! found && p >= last) + { + ec = error::need_more; + return; + } + for(;;) + { + if(*p == ':') + break; + if(! is_token[static_cast< + unsigned char>(*p)]) + { + ec = error::bad_field; + return; + } + ++p; + if(p >= last) + { + ec = error::need_more; + return; + } + } + if(p == first) + { + // empty name + ec = error::bad_field; + return; + } + name = make_string(first, p); + ++p; // eat ':' + char const* token_last; + for(;;) + { + // eat leading ' ' and '\t' + for(;;++p) + { + if(p + 1 > last) + { + ec = error::need_more; + return; + } + if(! (*p == ' ' || *p == '\t')) + break; + } + // parse to CRLF + first = p; + p = parse_token_to_eol(p, last, token_last, ec); + if(ec) + return; + if(! p) + { + ec = error::bad_value; + return; + } + // Look 1 char past the CRLF to handle obs-fold. + if(p + 1 > last) + { + ec = error::need_more; + return; + } + token_last = + trim_back(token_last, first); + if(*p != ' ' && *p != '\t') + { + value = make_string(first, token_last); + return; + } + ++p; + if(token_last != first) + break; + } + buf.resize(0); + buf.append(first, token_last); + BOOST_ASSERT(! buf.empty()); + try + { + for(;;) + { + // eat leading ' ' and '\t' + for(;;++p) + { + if(p + 1 > last) + { + ec = error::need_more; + return; + } + if(! (*p == ' ' || *p == '\t')) + break; + } + // parse to CRLF + first = p; + p = parse_token_to_eol(p, last, token_last, ec); + if(ec) + return; + if(! p) + { + ec = error::bad_value; + return; + } + // Look 1 char past the CRLF to handle obs-fold. + if(p + 1 > last) + { + ec = error::need_more; + return; + } + token_last = trim_back(token_last, first); + if(first != token_last) + { + buf.push_back(' '); + buf.append(first, token_last); + } + if(*p != ' ' && *p != '\t') + { + value = {buf.data(), buf.size()}; + return; + } + ++p; + } + } + catch(std::length_error const&) + { + ec = error::header_limit; + return; + } + } +}; + +} // detail +} // http +} // beast + +#endif diff --git a/include/beast/http/detail/basic_parser_v1.hpp b/include/beast/http/detail/basic_parser_v1.hpp deleted file mode 100644 index 2df947d142..0000000000 --- a/include/beast/http/detail/basic_parser_v1.hpp +++ /dev/null @@ -1,146 +0,0 @@ -// -// 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_HTTP_DETAIL_BASIC_PARSER_V1_HPP -#define BEAST_HTTP_DETAIL_BASIC_PARSER_V1_HPP - -#include - -namespace beast { -namespace http { -namespace detail { - -template -struct parser_str_t -{ - static char constexpr close[6] = "close"; - static char constexpr chunked[8] = "chunked"; - static char constexpr keep_alive[11] = "keep-alive"; - - static char constexpr upgrade[8] = "upgrade"; - static char constexpr connection[11] = "connection"; - static char constexpr content_length[15] = "content-length"; - static char constexpr proxy_connection[17] = "proxy-connection"; - static char constexpr transfer_encoding[18] = "transfer-encoding"; -}; - -template -char constexpr -parser_str_t<_>::close[6]; - -template -char constexpr -parser_str_t<_>::chunked[8]; - -template -char constexpr -parser_str_t<_>::keep_alive[11]; - -template -char constexpr -parser_str_t<_>::upgrade[8]; - -template -char constexpr -parser_str_t<_>::connection[11]; - -template -char constexpr -parser_str_t<_>::content_length[15]; - -template -char constexpr -parser_str_t<_>::proxy_connection[17]; - -template -char constexpr -parser_str_t<_>::transfer_encoding[18]; - -using parser_str = parser_str_t<>; - -class parser_base -{ -protected: - enum state : std::uint8_t - { - s_dead = 1, - - s_req_start, - s_req_method0, - s_req_method, - s_req_url0, - s_req_url, - s_req_http, - s_req_http_H, - s_req_http_HT, - s_req_http_HTT, - s_req_http_HTTP, - s_req_major, - s_req_dot, - s_req_minor, - s_req_cr, - s_req_lf, - - s_res_start, - s_res_H, - s_res_HT, - s_res_HTT, - s_res_HTTP, - s_res_major, - s_res_dot, - s_res_minor, - s_res_space_1, - s_res_status0, - s_res_status1, - s_res_status2, - s_res_space_2, - s_res_reason0, - s_res_reason, - s_res_line_lf, - s_res_line_done, - - s_header_name0, - s_header_name, - s_header_value0_lf, - s_header_value0_almost_done, - s_header_value0, - s_header_value, - s_header_value_lf, - s_header_value_almost_done, - s_header_value_unfold, - - s_headers_almost_done, - s_headers_done, - - s_chunk_size0, - s_chunk_size, - s_chunk_ext_name0, - s_chunk_ext_name, - s_chunk_ext_val, - s_chunk_size_lf, - s_chunk_data0, - s_chunk_data, - s_chunk_data_cr, - s_chunk_data_lf, - - s_body_pause, - s_body_identity0, - s_body_identity, - s_body_identity_eof0, - s_body_identity_eof, - - s_complete, - s_restart, - s_closed_complete - }; -}; - -} // detail -} // http -} // beast - -#endif diff --git a/include/beast/http/detail/chunk_encode.hpp b/include/beast/http/detail/chunk_encode.hpp index f496dcfe86..059f36344c 100644 --- a/include/beast/http/detail/chunk_encode.hpp +++ b/include/beast/http/detail/chunk_encode.hpp @@ -17,20 +17,22 @@ namespace beast { namespace http { namespace detail { -class chunk_encode_delim +/** A buffer sequence containing a chunk-encoding header +*/ +class chunk_header { boost::asio::const_buffer cb_; - // Storage for the longest hex string we might need, plus delimiters. - std::array buf_; + // Storage for the longest hex string we might need + char buf_[2 * sizeof(std::size_t)]; template void - copy(chunk_encode_delim const& other); + copy(chunk_header const& other); template void - setup(std::size_t n); + prepare_impl(std::size_t n); template static @@ -55,15 +57,27 @@ public: using const_iterator = value_type const*; - chunk_encode_delim(chunk_encode_delim const& other) + /** Constructor (default) + + Default-constructed chunk headers are in an + undefined state. + */ + chunk_header() = default; + + /// Copy constructor + chunk_header(chunk_header const& other) { copy(other); } + /** Construct a chunk header + + @param n The number of octets in this chunk. + */ explicit - chunk_encode_delim(std::size_t n) + chunk_header(std::size_t n) { - setup(n); + prepare_impl(n); } const_iterator @@ -77,31 +91,55 @@ public: { return begin() + 1; } + + void + prepare(std::size_t n) + { + prepare_impl(n); + } }; template void -chunk_encode_delim:: -copy(chunk_encode_delim const& other) +chunk_header:: +copy(chunk_header const& other) { + using boost::asio::buffer_copy; auto const n = boost::asio::buffer_size(other.cb_); - buf_ = other.buf_; - cb_ = boost::asio::const_buffer( - &buf_[buf_.size() - n], n); + auto const mb = boost::asio::mutable_buffers_1( + &buf_[sizeof(buf_) - n], n); + cb_ = *mb.begin(); + buffer_copy(mb, + boost::asio::const_buffers_1(other.cb_)); } template void -chunk_encode_delim:: -setup(std::size_t n) +chunk_header:: +prepare_impl(std::size_t n) { - buf_[buf_.size() - 2] = '\r'; - buf_[buf_.size() - 1] = '\n'; - auto it = to_hex(buf_.end() - 2, n); + auto const end = &buf_[sizeof(buf_)]; + auto it = to_hex(end, n); cb_ = boost::asio::const_buffer{&*it, static_cast( - std::distance(it, buf_.end()))}; + std::distance(it, end))}; +} + +/// Returns a buffer sequence holding a CRLF for chunk encoding +inline +boost::asio::const_buffers_1 +chunk_crlf() +{ + return {"\r\n", 2}; +} + +/// Returns a buffer sequence holding a final chunk header +inline +boost::asio::const_buffers_1 +chunk_final() +{ + return {"0\r\n", 3}; } } // detail diff --git a/include/beast/http/detail/rfc7230.hpp b/include/beast/http/detail/rfc7230.hpp index a68b4da835..cd7093d9ef 100644 --- a/include/beast/http/detail/rfc7230.hpp +++ b/include/beast/http/detail/rfc7230.hpp @@ -8,7 +8,7 @@ #ifndef BEAST_HTTP_DETAIL_RFC7230_HPP #define BEAST_HTTP_DETAIL_RFC7230_HPP -#include +#include #include #include @@ -45,8 +45,8 @@ is_alpha(char c) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 224 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 240 }; - static_assert(sizeof(tab) == 256, ""); - return tab[static_cast(c)]; + BOOST_STATIC_ASSERT(sizeof(tab) == 256); + return tab[static_cast(c)]; } inline @@ -72,8 +72,8 @@ is_text(char c) 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 224 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240 }; - static_assert(sizeof(tab) == 256, ""); - return tab[static_cast(c)]; + BOOST_STATIC_ASSERT(sizeof(tab) == 256); + return tab[static_cast(c)]; } inline @@ -104,8 +104,8 @@ is_tchar(char c) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 224 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 240 }; - static_assert(sizeof(tab) == 256, ""); - return tab[static_cast(c)]; + BOOST_STATIC_ASSERT(sizeof(tab) == 256); + return tab[static_cast(c)]; } inline @@ -133,8 +133,8 @@ is_qdchar(char c) 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 224 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240 }; - static_assert(sizeof(tab) == 256, ""); - return tab[static_cast(c)]; + BOOST_STATIC_ASSERT(sizeof(tab) == 256); + return tab[static_cast(c)]; } inline @@ -163,44 +163,8 @@ is_qpchar(char c) 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 224 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240 }; - static_assert(sizeof(tab) == 256, ""); - return tab[static_cast(c)]; -} - -// converts to lower case, -// returns 0 if not a valid token char -// -inline -char -to_field_char(char c) -{ - /* token = 1* - CHAR = - sep = "(" | ")" | "<" | ">" | "@" - | "," | ";" | ":" | "\" | <"> - | "/" | "[" | "]" | "?" | "=" - | "{" | "}" | SP | HT - */ - static char constexpr tab[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, '!', 0, '#', '$', '%', '&', '\'', 0, 0, '*', '+', 0, '-', '.', 0, - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 0, 0, 0, 0, 0, 0, - 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 0, 0, '^', '_', - '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, '|', 0, '~', 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; - static_assert(sizeof(tab) == 256, ""); - return tab[static_cast(c)]; + BOOST_STATIC_ASSERT(sizeof(tab) == 256); + return tab[static_cast(c)]; } // converts to lower case, @@ -229,15 +193,16 @@ to_value_char(char c) 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, // 224 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 // 240 }; - static_assert(sizeof(tab) == 256, ""); - return static_cast(tab[static_cast(c)]); + BOOST_STATIC_ASSERT(sizeof(tab) == 256); + return static_cast(tab[static_cast(c)]); } +// VFALCO TODO Make this return unsigned? inline std::int8_t unhex(char c) { - static char constexpr tab[] = { + static signed char constexpr tab[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 32 @@ -255,23 +220,69 @@ unhex(char c) -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 224 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // 240 }; - static_assert(sizeof(tab) == 256, ""); - return tab[static_cast(c)]; + BOOST_STATIC_ASSERT(sizeof(tab) == 256); + return tab[static_cast(c)]; } template +inline void skip_ows(FwdIt& it, FwdIt const& end) { while(it != end) { - auto const c = *it; - if(c != ' ' && c != '\t') + if(*it != ' ' && *it != '\t') break; ++it; } } +template +inline +void +skip_ows_rev( + RanIt& it, RanIt const& first) +{ + while(it != first) + { + auto const c = it[-1]; + if(c != ' ' && c != '\t') + break; + --it; + } +} + +// obs-fold = CRLF 1*( SP / HTAB ) +// return `false` on parse error +// +template +inline +bool +skip_obs_fold( + FwdIt& it, FwdIt const& last) +{ + for(;;) + { + if(*it != '\r') + return true; + if(++it == last) + return false; + if(*it != '\n') + return false; + if(++it == last) + return false; + if(*it != ' ' && *it != '\t') + return false; + for(;;) + { + if(++it == last) + return true; + if(*it != ' ' && *it != '\t') + return true; + } + } +} + template void skip_token(FwdIt& it, FwdIt const& last) @@ -281,8 +292,8 @@ skip_token(FwdIt& it, FwdIt const& last) } inline -boost::string_ref -trim(boost::string_ref const& s) +string_view +trim(string_view s) { auto first = s.begin(); auto last = s.end(); @@ -302,12 +313,12 @@ trim(boost::string_ref const& s) struct param_iter { - using iter_type = boost::string_ref::const_iterator; + using iter_type = string_view::const_iterator; iter_type it; iter_type first; iter_type last; - std::pair v; + std::pair v; bool empty() const @@ -403,6 +414,53 @@ increment() } } +/* + #token = [ ( "," / token ) *( OWS "," [ OWS token ] ) ] +*/ +struct opt_token_list_policy +{ + using value_type = string_view; + + bool + operator()(value_type& v, + char const*& it, string_view s) const + { + v = {}; + auto need_comma = it != s.begin(); + for(;;) + { + detail::skip_ows(it, s.end()); + if(it == s.end()) + { + it = nullptr; + return true; + } + auto const c = *it; + if(detail::is_tchar(c)) + { + if(need_comma) + return false; + auto const p0 = it; + for(;;) + { + ++it; + if(it == s.end()) + break; + if(! detail::is_tchar(*it)) + break; + } + v = string_view{&*p0, + static_cast(it - p0)}; + return true; + } + if(c != ',') + return false; + need_comma = false; + ++it; + } + } +}; + } // detail } // http } // beast diff --git a/include/beast/http/detail/type_traits.hpp b/include/beast/http/detail/type_traits.hpp new file mode 100644 index 0000000000..bb10a2d7f5 --- /dev/null +++ b/include/beast/http/detail/type_traits.hpp @@ -0,0 +1,188 @@ +// +// 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_HTTP_DETAIL_TYPE_TRAITS_HPP +#define BEAST_HTTP_DETAIL_TYPE_TRAITS_HPP + +#include +#include +#include + +namespace beast { +namespace http { + +template +struct header; + +template +struct message; + +template +class parser; + +namespace detail { + +template +class is_header_impl +{ + template + static std::true_type check( + header const*); + static std::false_type check(...); +public: + using type = decltype(check((T*)0)); +}; + +template +using is_header = typename is_header_impl::type; + +template +struct is_parser : std::false_type {}; + +template +struct is_parser> : std::true_type {}; + +struct fields_model +{ + string_view method() const; + string_view reason() const; + string_view target() const; + +protected: + string_view get_method_impl() const; + string_view get_target_impl() const; + string_view get_reason_impl() const; + bool get_chunked_impl() const; + bool get_keep_alive_impl(unsigned) const; + void set_method_impl(string_view); + void set_target_impl(string_view); + void set_reason_impl(string_view); + void set_chunked_impl(bool); + void set_content_length_impl(boost::optional); + void set_keep_alive_impl(unsigned, bool); +}; + +template> +struct has_value_type : std::false_type {}; + +template +struct has_value_type > : std::true_type {}; + +/** Determine if a @b Body type has a size + + This metafunction is equivalent to `std::true_type` if + Body contains a static member function called `size`. +*/ +template +struct is_body_sized : std::false_type {}; + +template +struct is_body_sized() = + T::size(std::declval()), + (void)0)>> : std::true_type {}; + +template +struct is_fields_helper : T +{ + template + static auto f1(int) -> decltype( + std::declval() = std::declval().get_method_impl(), + std::true_type()); + static auto f1(...) -> std::false_type; + using t1 = decltype(f1(0)); + + template + static auto f2(int) -> decltype( + std::declval() = std::declval().get_target_impl(), + std::true_type()); + static auto f2(...) -> std::false_type; + using t2 = decltype(f2(0)); + + template + static auto f3(int) -> decltype( + std::declval() = std::declval().get_reason_impl(), + std::true_type()); + static auto f3(...) -> std::false_type; + using t3 = decltype(f3(0)); + + template + static auto f4(int) -> decltype( + std::declval() = std::declval().get_chunked_impl(), + std::true_type()); + static auto f4(...) -> std::false_type; + using t4 = decltype(f4(0)); + + template + static auto f5(int) -> decltype( + std::declval() = std::declval().get_keep_alive_impl( + std::declval()), + std::true_type()); + static auto f5(...) -> std::false_type; + using t5 = decltype(f5(0)); + + template + static auto f6(int) -> decltype( + void(std::declval().set_method_impl(std::declval())), + std::true_type()); + static auto f6(...) -> std::false_type; + using t6 = decltype(f6(0)); + + template + static auto f7(int) -> decltype( + void(std::declval().set_target_impl(std::declval())), + std::true_type()); + static auto f7(...) -> std::false_type; + using t7 = decltype(f7(0)); + + template + static auto f8(int) -> decltype( + void(std::declval().set_reason_impl(std::declval())), + std::true_type()); + static auto f8(...) -> std::false_type; + using t8 = decltype(f8(0)); + + template + static auto f9(int) -> decltype( + void(std::declval().set_chunked_impl(std::declval())), + std::true_type()); + static auto f9(...) -> std::false_type; + using t9 = decltype(f9(0)); + + template + static auto f10(int) -> decltype( + void(std::declval().set_content_length_impl( + std::declval>())), + std::true_type()); + static auto f10(...) -> std::false_type; + using t10 = decltype(f10(0)); + + template + static auto f11(int) -> decltype( + void(std::declval().set_keep_alive_impl( + std::declval(), + std::declval())), + std::true_type()); + static auto f11(...) -> std::false_type; + using t11 = decltype(f11(0)); + + using type = std::integral_constant; +}; + +} // detail +} // http +} // beast + +#endif diff --git a/include/beast/http/dynamic_body.hpp b/include/beast/http/dynamic_body.hpp new file mode 100644 index 0000000000..c51420848e --- /dev/null +++ b/include/beast/http/dynamic_body.hpp @@ -0,0 +1,168 @@ +// +// 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_HTTP_DYNAMIC_BODY_HPP +#define BEAST_HTTP_DYNAMIC_BODY_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** A @b Body using a @b DynamicBuffer + + This body uses a @b DynamicBuffer as a memory-based container + for holding message payloads. Messages using this body type + may be serialized and parsed. +*/ +template +struct basic_dynamic_body +{ + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + + /** The type of container used for the body + + This determines the type of @ref message::body + when this body type is used with a message container. + */ + using value_type = DynamicBuffer; + + /** Returns the payload size of the body + + When this body is used with @ref message::prepare_payload, + the Content-Length will be set to the payload size, and + any chunked Transfer-Encoding will be removed. + */ + static + std::uint64_t + size(value_type const& v) + { + return v.size(); + } + + /** The algorithm for serializing the body + + Meets the requirements of @b BodyReader. + */ +#if BEAST_DOXYGEN + using reader = implementation_defined; +#else + class reader + { + DynamicBuffer const& body_; + + public: + using const_buffers_type = + typename DynamicBuffer::const_buffers_type; + + template + explicit + reader(message const& m) + : body_(m.body) + { + } + + void + init(error_code& ec) + { + ec.assign(0, ec.category()); + } + + boost::optional> + get(error_code& ec) + { + ec.assign(0, ec.category()); + return {{body_.data(), false}}; + } + }; +#endif + + /** The algorithm for parsing the body + + Meets the requirements of @b BodyReader. + */ +#if BEAST_DOXYGEN + using writer = implementation_defined; +#else + class writer + { + value_type& body_; + + public: + template + explicit + writer(message& msg) + : body_(msg.body) + { + } + + void + init(boost::optional const&, error_code& ec) + { + ec.assign(0, ec.category()); + } + + template + std::size_t + put(ConstBufferSequence const& buffers, + error_code& ec) + { + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + auto const n = buffer_size(buffers); + if(body_.size() > body_.max_size() - n) + { + ec = error::buffer_overflow; + return 0; + } + boost::optional b; + try + { + b.emplace(body_.prepare((std::min)(n, + body_.max_size() - body_.size()))); + } + catch(std::length_error const&) + { + ec = error::buffer_overflow; + return 0; + } + ec.assign(0, ec.category()); + auto const bytes_transferred = + buffer_copy(*b, buffers); + body_.commit(bytes_transferred); + return bytes_transferred; + } + + void + finish(error_code& ec) + { + ec.assign(0, ec.category()); + } + }; +#endif +}; + +/** A dynamic message body represented by a @ref multi_buffer + + Meets the requirements of @b Body. +*/ +using dynamic_body = basic_dynamic_body; + +} // http +} // beast + +#endif diff --git a/include/beast/http/empty_body.hpp b/include/beast/http/empty_body.hpp index 40f549d9f4..2db58a011b 100644 --- a/include/beast/http/empty_body.hpp +++ b/include/beast/http/empty_body.hpp @@ -9,62 +9,117 @@ #define BEAST_HTTP_EMPTY_BODY_HPP #include -#include +#include #include -#include -#include -#include -#include +#include namespace beast { namespace http { -/** An empty content-body. +/** An empty @b Body - Meets the requirements of @b `Body`. + This body is used to represent messages which do not have a + message body. If this body is used with a parser, and the + parser encounters octets corresponding to a message body, + the parser will fail with the error @ref http::unexpected_body. + + The Content-Length of this body is always 0. */ struct empty_body { -#if GENERATING_DOCS - /// The type of the `message::body` member - using value_type = void; + /** The type of container used for the body + + This determines the type of @ref message::body + when this body type is used with a message container. + */ + struct value_type + { + }; + + /** Returns the payload size of the body + + When this body is used with @ref message::prepare_payload, + the Content-Length will be set to the payload size, and + any chunked Transfer-Encoding will be removed. + */ + static + std::uint64_t + size(value_type) + { + return 0; + } + + /** The algorithm for serializing the body + + Meets the requirements of @b BodyReader. + */ +#if BEAST_DOXYGEN + using reader = implementation_defined; #else - struct value_type {}; + struct reader + { + using const_buffers_type = + boost::asio::null_buffers; + + template + explicit + reader(message const&) + { + } + + void + init(error_code& ec) + { + ec.assign(0, ec.category()); + } + + boost::optional> + get(error_code& ec) + { + ec.assign(0, ec.category()); + return boost::none; + } + }; #endif -#if GENERATING_DOCS -private: -#endif + /** The algorithm for parsing the body + Meets the requirements of @b BodyReader. + */ +#if BEAST_DOXYGEN + using writer = implementation_defined; +#else struct writer { template explicit - writer(message const& m) noexcept + writer(message&) { - beast::detail::ignore_unused(m); } void - init(error_code& ec) noexcept + init(boost::optional const&, error_code& ec) { - beast::detail::ignore_unused(ec); + ec.assign(0, ec.category()); } - std::uint64_t - content_length() const noexcept + template + std::size_t + put(ConstBufferSequence const&, + error_code& ec) { + ec = error::unexpected_body; return 0; } - template - bool - write(error_code&, WriteFunction&& wf) noexcept + void + finish(error_code& ec) { - wf(boost::asio::null_buffers{}); - return true; + ec.assign(0, ec.category()); } }; +#endif }; } // http diff --git a/include/beast/http/error.hpp b/include/beast/http/error.hpp new file mode 100644 index 0000000000..861620e342 --- /dev/null +++ b/include/beast/http/error.hpp @@ -0,0 +1,150 @@ +// +// 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_HTTP_ERROR_HPP +#define BEAST_HTTP_ERROR_HPP + +#include +#include + +namespace beast { +namespace http { + +/// Error codes returned from HTTP algorithms and operations. +enum class error +{ + /** The end of the stream was reached. + + This error is returned under the following conditions: + + @li When attempting to read HTTP data from a stream and the stream + read returns the error `boost::asio::error::eof` before any new octets + have been received. + + @li When sending a complete HTTP message at once and the semantics of + the message are that the connection should be closed to indicate the + end of the message. + */ + end_of_stream = 1, + + /** The incoming message is incomplete. + + This happens when the end of stream is reached during + parsing and some octets have been received, but not the + entire message. + */ + partial_message, + + /** Additional buffers are required. + + This error is returned during parsing when additional + octets are needed. The caller should append more data + to the existing buffer and retry the parse operaetion. + */ + need_more, + + /** An unexpected body was encountered during parsing. + + This error is returned when attempting to parse body + octets into a message container which has the + @ref empty_body body type. + + @see @ref empty_body + */ + unexpected_body, + + /** Additional buffers are required. + + This error is returned under the following conditions: + + @li During serialization when using @ref buffer_body. + The caller should update the body to point to a new + buffer or indicate that there are no more octets in + the body. + + @li During parsing when using @ref buffer_body. + The caller should update the body to point to a new + storage area to receive additional body octets. + */ + need_buffer, + + /** Buffer maximum exceeded. + + This error is returned when reading HTTP content + into a dynamic buffer, and the operation would + exceed the maximum size of the buffer. + */ + buffer_overflow, + + /** Header limit exceeded. + + The parser detected an incoming message header which + exceeded a configured limit. + */ + header_limit, + + /** Body limit exceeded. + + The parser detected an incoming message body which + exceeded a configured limit. + */ + body_limit, + + /** A memory allocation failed. + + When basic_fields throws std::bad_alloc, it is + converted into this error by @ref parser. + */ + bad_alloc, + + // + // (parser errors) + // + + /// The line ending was malformed + bad_line_ending, + + /// The method is invalid. + bad_method, + + /// The request-target is invalid. + bad_target, + + /// The HTTP-version is invalid. + bad_version, + + /// The status-code is invalid. + bad_status, + + /// The reason-phrase is invalid. + bad_reason, + + /// The field name is invalid. + bad_field, + + /// The field value is invalid. + bad_value, + + /// The Content-Length is invalid. + bad_content_length, + + /// The Transfer-Encoding is invalid. + bad_transfer_encoding, + + /// The chunk syntax is invalid. + bad_chunk, + + /// An obs-fold exceeded an internal limit. + bad_obs_fold +}; + +} // http +} // beast + +#include + +#endif diff --git a/include/beast/http/field.hpp b/include/beast/http/field.hpp new file mode 100644 index 0000000000..62d6a3f7bf --- /dev/null +++ b/include/beast/http/field.hpp @@ -0,0 +1,405 @@ +// +// 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_HTTP_FIELD_HPP +#define BEAST_HTTP_FIELD_HPP + +#include +#include +#include + +namespace beast { +namespace http { + +enum class field : unsigned short +{ + unknown = 0, + + a_im, + accept, + accept_additions, + accept_charset, + accept_datetime, + accept_encoding, + accept_features, + accept_language, + accept_patch, + accept_post, + accept_ranges, + access_control, + access_control_allow_credentials, + access_control_allow_headers, + access_control_allow_methods, + access_control_allow_origin, + access_control_max_age, + access_control_request_headers, + access_control_request_method, + age, + allow, + alpn, + also_control, + alt_svc, + alt_used, + alternate_recipient, + alternates, + apparently_to, + apply_to_redirect_ref, + approved, + archive, + archived_at, + article_names, + article_updates, + authentication_control, + authentication_info, + authentication_results, + authorization, + auto_submitted, + autoforwarded, + autosubmitted, + base, + bcc, + body, + c_ext, + c_man, + c_opt, + c_pep, + c_pep_info, + cache_control, + caldav_timezones, + cancel_key, + cancel_lock, + cc, + close, + comments, + compliance, + connection, + content_alternative, + content_base, + content_description, + content_disposition, + content_duration, + content_encoding, + content_features, + content_id, + content_identifier, + content_language, + content_length, + content_location, + content_md5, + content_range, + content_return, + content_script_type, + content_style_type, + content_transfer_encoding, + content_type, + content_version, + control, + conversion, + conversion_with_loss, + cookie, + cookie2, + cost, + dasl, + date, + date_received, + dav, + default_style, + deferred_delivery, + delivery_date, + delta_base, + depth, + derived_from, + destination, + differential_id, + digest, + discarded_x400_ipms_extensions, + discarded_x400_mts_extensions, + disclose_recipients, + disposition_notification_options, + disposition_notification_to, + distribution, + dkim_signature, + dl_expansion_history, + downgraded_bcc, + downgraded_cc, + downgraded_disposition_notification_to, + downgraded_final_recipient, + downgraded_from, + downgraded_in_reply_to, + downgraded_mail_from, + downgraded_message_id, + downgraded_original_recipient, + downgraded_rcpt_to, + downgraded_references, + downgraded_reply_to, + downgraded_resent_bcc, + downgraded_resent_cc, + downgraded_resent_from, + downgraded_resent_reply_to, + downgraded_resent_sender, + downgraded_resent_to, + downgraded_return_path, + downgraded_sender, + downgraded_to, + ediint_features, + eesst_version, + encoding, + encrypted, + errors_to, + etag, + expect, + expires, + expiry_date, + ext, + followup_to, + forwarded, + from, + generate_delivery_report, + getprofile, + hobareg, + host, + http2_settings, + if_, + if_match, + if_modified_since, + if_none_match, + if_range, + if_schedule_tag_match, + if_unmodified_since, + im, + importance, + in_reply_to, + incomplete_copy, + injection_date, + injection_info, + jabber_id, + keep_alive, + keywords, + label, + language, + last_modified, + latest_delivery_time, + lines, + link, + list_archive, + list_help, + list_id, + list_owner, + list_post, + list_subscribe, + list_unsubscribe, + list_unsubscribe_post, + location, + lock_token, + man, + max_forwards, + memento_datetime, + message_context, + message_id, + message_type, + meter, + method_check, + method_check_expires, + mime_version, + mmhs_acp127_message_identifier, + mmhs_authorizing_users, + mmhs_codress_message_indicator, + mmhs_copy_precedence, + mmhs_exempted_address, + mmhs_extended_authorisation_info, + mmhs_handling_instructions, + mmhs_message_instructions, + mmhs_message_type, + mmhs_originator_plad, + mmhs_originator_reference, + mmhs_other_recipients_indicator_cc, + mmhs_other_recipients_indicator_to, + mmhs_primary_precedence, + mmhs_subject_indicator_codes, + mt_priority, + negotiate, + newsgroups, + nntp_posting_date, + nntp_posting_host, + non_compliance, + obsoletes, + opt, + optional, + optional_www_authenticate, + ordering_type, + organization, + origin, + original_encoded_information_types, + original_from, + original_message_id, + original_recipient, + original_sender, + original_subject, + originator_return_address, + overwrite, + p3p, + path, + pep, + pep_info, + pics_label, + position, + posting_version, + pragma, + prefer, + preference_applied, + prevent_nondelivery_report, + priority, + privicon, + profileobject, + protocol, + protocol_info, + protocol_query, + protocol_request, + proxy_authenticate, + proxy_authentication_info, + proxy_authorization, + proxy_connection, + proxy_features, + proxy_instruction, + public_, + public_key_pins, + public_key_pins_report_only, + range, + received, + received_spf, + redirect_ref, + references, + referer, + referer_root, + relay_version, + reply_by, + reply_to, + require_recipient_valid_since, + resent_bcc, + resent_cc, + resent_date, + resent_from, + resent_message_id, + resent_reply_to, + resent_sender, + resent_to, + resolution_hint, + resolver_location, + retry_after, + return_path, + safe, + schedule_reply, + schedule_tag, + sec_websocket_accept, + sec_websocket_extensions, + sec_websocket_key, + sec_websocket_protocol, + sec_websocket_version, + security_scheme, + see_also, + sender, + sensitivity, + server, + set_cookie, + set_cookie2, + setprofile, + sio_label, + sio_label_history, + slug, + soapaction, + solicitation, + status_uri, + strict_transport_security, + subject, + subok, + subst, + summary, + supersedes, + surrogate_capability, + surrogate_control, + tcn, + te, + timeout, + title, + to, + topic, + trailer, + transfer_encoding, + ttl, + ua_color, + ua_media, + ua_pixels, + ua_resolution, + ua_windowpixels, + upgrade, + urgency, + uri, + user_agent, + variant_vary, + vary, + vbr_info, + version, + via, + want_digest, + warning, + www_authenticate, + x_archived_at, + x_device_accept, + x_device_accept_charset, + x_device_accept_encoding, + x_device_accept_language, + x_device_user_agent, + x_frame_options, + x_mittente, + x_pgp_sig, + x_ricevuta, + x_riferimento_message_id, + x_tiporicevuta, + x_trasporto, + x_verificasicurezza, + x400_content_identifier, + x400_content_return, + x400_content_type, + x400_mts_identifier, + x400_originator, + x400_received, + x400_recipients, + x400_trace, + xref, +}; + +/** Convert a field enum to a string. + + @param f The field to convert +*/ +string_view +to_string(field f); + +/** Attempt to convert a string to a field enum. + + The string comparison is case-insensitive. + + @return The corresponding field, or @ref field::unknown + if no known field matches. +*/ +field +string_to_field(string_view s); + +/// Write the text for a field name to an output stream. +inline +std::ostream& +operator<<(std::ostream& os, field f) +{ + return os << to_string(f); +} + +} // http +} // beast + +#include + +#endif diff --git a/include/beast/http/fields.hpp b/include/beast/http/fields.hpp index 7c2ef2a59a..005c4ab2db 100644 --- a/include/beast/http/fields.hpp +++ b/include/beast/http/fields.hpp @@ -9,17 +9,709 @@ #define BEAST_HTTP_FIELDS_HPP #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include namespace beast { namespace http { +/** A container for storing HTTP header fields. + + This container is designed to store the field value pairs that make + up the fields and trailers in an HTTP message. Objects of this type + are iterable, with each element holding the field name and field + value. + + Field names are stored as-is, but comparisons are case-insensitive. + The container behaves as a `std::multiset`; there will be a separate + value for each occurrence of the same field name. When the container + is iterated the fields are presented in the order of insertion, with + fields having the same name following each other consecutively. + + Meets the requirements of @b Fields + + @tparam Allocator The allocator to use. This must meet the + requirements of @b Allocator. +*/ +template +class basic_fields +{ + static std::size_t constexpr max_static_buffer = 4096; + + using off_t = std::uint16_t; + +public: + /// The type of allocator used. + using allocator_type = Allocator; + + /// The type of element used to represent a field + class value_type + { + friend class basic_fields; + + boost::asio::const_buffer + buffer() const; + + value_type(field name, + string_view sname, string_view value); + + boost::intrusive::list_member_hook< + boost::intrusive::link_mode< + boost::intrusive::normal_link>> + list_hook_; + boost::intrusive::set_member_hook< + boost::intrusive::link_mode< + boost::intrusive::normal_link>> + set_hook_; + off_t off_; + off_t len_; + field f_; + + public: + /// Returns the field enum, which can be @ref field::unknown + field + name() const; + + /// Returns the field name as a string + string_view + name_string() const; + + /// Returns the value of the field + string_view + value() const; + }; + + /** A strictly less predicate for comparing keys, using a case-insensitive comparison. + + The case-comparison operation is defined only for low-ASCII characters. + */ + struct key_compare : beast::iless + { + /// Returns `true` if lhs is less than rhs using a strict ordering + template + bool + operator()(String const& lhs, value_type const& rhs) const + { + if(lhs.size() < rhs.name_string().size()) + return true; + if(lhs.size() > rhs.name_string().size()) + return false; + return iless::operator()(lhs, rhs.name_string()); + } + + /// Returns `true` if lhs is less than rhs using a strict ordering + template + bool + operator()(value_type const& lhs, String const& rhs) const + { + if(lhs.name_string().size() < rhs.size()) + return true; + if(lhs.name_string().size() > rhs.size()) + return false; + return iless::operator()(lhs.name_string(), rhs); + } + + /// Returns `true` if lhs is less than rhs using a strict ordering + bool + operator()(value_type const& lhs, value_type const& rhs) const + { + if(lhs.name_string().size() < rhs.name_string().size()) + return true; + if(lhs.name_string().size() > rhs.name_string().size()) + return false; + return iless::operator()(lhs.name_string(), rhs.name_string()); + } + }; + + /// The algorithm used to serialize the header +#if BEAST_DOXYGEN + using reader = implementation_defined; +#else + class reader; +#endif + +private: + using list_t = typename boost::intrusive::make_list< + value_type, boost::intrusive::member_hook< + value_type, boost::intrusive::list_member_hook< + boost::intrusive::link_mode< + boost::intrusive::normal_link>>, + &value_type::list_hook_>, + boost::intrusive::constant_time_size< + false>>::type; + + using set_t = typename boost::intrusive::make_multiset< + value_type, boost::intrusive::member_hook>, + &value_type::set_hook_>, + boost::intrusive::constant_time_size, + boost::intrusive::compare>::type; + + +protected: + friend class fields_test; // for `header` + + /// Destructor + ~basic_fields(); + + /// Constructor. + basic_fields() = default; + + /** Constructor. + + @param alloc The allocator to use. + */ + explicit + basic_fields(Allocator const& alloc); + + /** Move constructor. + + The state of the moved-from object is + as if constructed using the same allocator. + */ + basic_fields(basic_fields&&); + + /** Move constructor. + + The state of the moved-from object is + as if constructed using the same allocator. + + @param alloc The allocator to use. + */ + basic_fields(basic_fields&&, Allocator const& alloc); + + /// Copy constructor. + basic_fields(basic_fields const&); + + /** Copy constructor. + + @param alloc The allocator to use. + */ + basic_fields(basic_fields const&, Allocator const& alloc); + + /// Copy constructor. + template + basic_fields(basic_fields const&); + + /** Copy constructor. + + @param alloc The allocator to use. + */ + template + basic_fields(basic_fields const&, + Allocator const& alloc); + + /** Move assignment. + + The state of the moved-from object is + as if constructed using the same allocator. + */ + basic_fields& operator=(basic_fields&&); + + /// Copy assignment. + basic_fields& operator=(basic_fields const&); + + /// Copy assignment. + template + basic_fields& operator=(basic_fields const&); + +public: + /// A constant iterator to the field sequence. +#if BEAST_DOXYGEN + using const_iterator = implementation_defined; +#else + using const_iterator = typename list_t::const_iterator; +#endif + + /// A constant iterator to the field sequence. + using iterator = const_iterator; + + /// Return a copy of the allocator associated with the container. + allocator_type + get_allocator() const + { + return typename std::allocator_traits< + Allocator>::template rebind_alloc< + value_type>(alloc_); + } + + //-------------------------------------------------------------------------- + // + // Element access + // + //-------------------------------------------------------------------------- + + /** Returns the value for a field, or throws an exception. + + @param name The name of the field. + + @return The field value. + + @throws std::out_of_range if the field is not found. + */ + string_view + at(field name) const; + + /** Returns the value for a field, or throws an exception. + + @param name The name of the field. + + @return The field value. + + @throws std::out_of_range if the field is not found. + */ + string_view + at(string_view name) const; + + /** Returns the value for a field, or `""` if it does not exist. + + If more than one field with the specified name exists, the + first field defined by insertion order is returned. + + @param name The name of the field. + */ + string_view + operator[](field name) const; + + /** Returns the value for a case-insensitive matching header, or `""` if it does not exist. + + If more than one field with the specified name exists, the + first field defined by insertion order is returned. + + @param name The name of the field. + */ + string_view + operator[](string_view name) const; + + //-------------------------------------------------------------------------- + // + // Iterators + // + //-------------------------------------------------------------------------- + + /// Return a const iterator to the beginning of the field sequence. + const_iterator + begin() const + { + return list_.cbegin(); + } + + /// Return a const iterator to the end of the field sequence. + const_iterator + end() const + { + return list_.cend(); + } + + /// Return a const iterator to the beginning of the field sequence. + const_iterator + cbegin() const + { + return list_.cbegin(); + } + + /// Return a const iterator to the end of the field sequence. + const_iterator + cend() const + { + return list_.cend(); + } + + //-------------------------------------------------------------------------- + // + // Capacity + // + //-------------------------------------------------------------------------- + +private: + // VFALCO Since the header and message derive from Fields, + // what does the expression m.empty() mean? Its confusing. + bool + empty() const + { + return list_.empty(); + } +public: + + //-------------------------------------------------------------------------- + // + // Modifiers + // + //-------------------------------------------------------------------------- + +private: + // VFALCO But this leaves behind the method, target, and reason! + /** Remove all fields from the container + + All references, pointers, or iterators referring to contained + elements are invalidated. All past-the-end iterators are also + invalidated. + */ + void + clear(); +public: + + /** Insert a field. + + If one or more fields with the same name already exist, + the new field will be inserted after the last field with + the matching name, in serialization order. + + @param name The field name. + + @param value The value of the field, as a @ref string_param + */ + void + insert(field name, string_param const& value); + + /** Insert a field. + + If one or more fields with the same name already exist, + the new field will be inserted after the last field with + the matching name, in serialization order. + + @param name The field name. + + @param value The value of the field, as a @ref string_param + */ + void + insert(string_view name, string_param const& value); + + /** Insert a field. + + If one or more fields with the same name already exist, + the new field will be inserted after the last field with + the matching name, in serialization order. + + @param name The field name. + + @param name_string The literal text corresponding to the + field name. If `name != field::unknown`, then this value + must be equal to `to_string(name)` using a case-insensitive + comparison, otherwise the behavior is undefined. + + @param value The value of the field, as a @ref string_param + */ + void + insert(field name, string_view name_string, + string_param const& value); + + /** Set a field value, removing any other instances of that field. + + First removes any values with matching field names, then + inserts the new field value. + + @param name The field name. + + @param value The value of the field, as a @ref string_param + + @return The field value. + */ + void + set(field name, string_param const& value); + + /** Set a field value, removing any other instances of that field. + + First removes any values with matching field names, then + inserts the new field value. + + @param name The field name. + + @param value The value of the field, as a @ref string_param + */ + void + set(string_view name, string_param const& value); + + /** Remove a field. + + References and iterators to the erased elements are + invalidated. Other references and iterators are not + affected. + + @param pos An iterator to the element to remove. + + @return An iterator following the last removed element. + If the iterator refers to the last element, the end() + iterator is returned. + */ + const_iterator + erase(const_iterator pos); + + /** Remove all fields with the specified name. + + All fields with the same field name are erased from the + container. + References and iterators to the erased elements are + invalidated. Other references and iterators are not + affected. + + @param name The field name. + + @return The number of fields removed. + */ + std::size_t + erase(field name); + + /** Remove all fields with the specified name. + + All fields with the same field name are erased from the + container. + References and iterators to the erased elements are + invalidated. Other references and iterators are not + affected. + + @param name The field name. + + @return The number of fields removed. + */ + std::size_t + erase(string_view name); + + /// Swap this container with another + void + swap(basic_fields& other); + + /// Swap two field containers + template + friend + void + swap(basic_fields& lhs, basic_fields& rhs); + + //-------------------------------------------------------------------------- + // + // Lookup + // + //-------------------------------------------------------------------------- + + /** Return the number of fields with the specified name. + + @param name The field name. + */ + std::size_t + count(field name) const; + + /** Return the number of fields with the specified name. + + @param name The field name. + */ + std::size_t + count(string_view name) const; + + /** Returns an iterator to the case-insensitive matching field. + + If more than one field with the specified name exists, the + first field defined by insertion order is returned. + + @param name The field name. + + @return An iterator to the matching field, or `end()` if + no match was found. + */ + const_iterator + find(field name) const; + + /** Returns an iterator to the case-insensitive matching field name. + + If more than one field with the specified name exists, the + first field defined by insertion order is returned. + + @param name The field name. + + @return An iterator to the matching field, or `end()` if + no match was found. + */ + const_iterator + find(string_view name) const; + + /** Returns a range of iterators to the fields with the specified name. + + @param name The field name. + + @return A range of iterators to fields with the same name, + otherwise an empty range. + */ + std::pair + equal_range(field name) const; + + /** Returns a range of iterators to the fields with the specified name. + + @param name The field name. + + @return A range of iterators to fields with the same name, + otherwise an empty range. + */ + std::pair + equal_range(string_view name) const; + + //-------------------------------------------------------------------------- + // + // Observers + // + //-------------------------------------------------------------------------- + + /// Returns a copy of the key comparison function + key_compare + key_comp() const + { + return key_compare{}; + } + +protected: + /** Returns the request-method string. + + @note Only called for requests. + */ + string_view + get_method_impl() const; + + /** Returns the request-target string. + + @note Only called for requests. + */ + string_view + get_target_impl() const; + + /** Returns the response reason-phrase string. + + @note Only called for responses. + */ + string_view + get_reason_impl() const; + + /** Returns the chunked Transfer-Encoding setting + */ + bool + get_chunked_impl() const; + + /** Returns the keep-alive setting + */ + bool + get_keep_alive_impl(unsigned version) const; + + /** Set or clear the method string. + + @note Only called for requests. + */ + void + set_method_impl(string_view s); + + /** Set or clear the target string. + + @note Only called for requests. + */ + void + set_target_impl(string_view s); + + /** Set or clear the reason string. + + @note Only called for responses. + */ + void + set_reason_impl(string_view s); + + /** Adjusts the chunked Transfer-Encoding value + */ + void + set_chunked_impl(bool value); + + /** Sets or clears the Content-Length field + */ + void + set_content_length_impl( + boost::optional const& value); + + /** Adjusts the Connection field + */ + void + set_keep_alive_impl( + unsigned version, bool keep_alive); + +private: + template + friend class basic_fields; + + using alloc_type = typename + std::allocator_traits:: + template rebind_alloc; + + using alloc_traits = + std::allocator_traits; + + using size_type = + typename std::allocator_traits::size_type; + + value_type& + new_element(field name, + string_view sname, string_view value); + + void + delete_element(value_type& e); + + void + set_element(value_type& e); + + void + realloc_string(string_view& dest, string_view s); + + void + realloc_target( + string_view& dest, string_view s); + + template + void + copy_all(basic_fields const&); + + void + clear_all(); + + void + delete_list(); + + void + move_assign(basic_fields&, std::true_type); + + void + move_assign(basic_fields&, std::false_type); + + void + copy_assign(basic_fields const&, std::true_type); + + void + copy_assign(basic_fields const&, std::false_type); + + void + swap(basic_fields& other, std::true_type); + + void + swap(basic_fields& other, std::false_type); + + alloc_type alloc_; + set_t set_; + list_t list_; + string_view method_; + string_view target_or_reason_; +}; + /// A typical HTTP header fields container -using fields = - basic_fields>; +using fields = basic_fields>; } // http } // beast +#include + #endif diff --git a/include/beast/http/file_body.hpp b/include/beast/http/file_body.hpp new file mode 100644 index 0000000000..bcc3ab407c --- /dev/null +++ b/include/beast/http/file_body.hpp @@ -0,0 +1,522 @@ +// +// 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_HTTP_FILE_BODY_HPP +#define BEAST_HTTP_FILE_BODY_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +//[example_http_file_body_1 + +/** A message body represented by a file on the filesystem. + + Messages with this type have bodies represented by a + file on the file system. When parsing a message using + this body type, the data is stored in the file pointed + to by the path, which must be writable. When serializing, + the implementation will read the file and present those + octets as the body content. This may be used to serve + content from a directory as part of a web service. + + @tparam File The implementation to use for accessing files. + This type must meet the requirements of @b File. +*/ +template +struct basic_file_body +{ + static_assert(is_file::value, + "File requirements not met"); + + /// The type of File this body uses + using file_type = File; + + /** Algorithm for retrieving buffers when serializing. + + Objects of this type are created during serialization + to extract the buffers representing the body. + */ + class reader; + + /** Algorithm for storing buffers when parsing. + + Objects of this type are created during parsing + to store incoming buffers representing the body. + */ + class writer; + + /** The type of the @ref message::body member. + + Messages declared using `basic_file_body` will have this + type for the body member. This rich class interface + allow the file to be opened with the file handle + maintained directly in the object, which is attached + to the message. + */ + class value_type; + + /** Returns the size of the body + + @param v The file body to use + */ + static + std::uint64_t + size(value_type const& v); +}; + +//] + +//[example_http_file_body_2 + +// The body container holds a handle to the file when +// it is open, and also caches the size when set. +// +template +class basic_file_body::value_type +{ + friend class reader; + friend class writer; + friend struct basic_file_body; + + // This represents the open file + File file_; + + // The cached file size + std::uint64_t file_size_ = 0; + +public: + /** Destructor. + + If the file is open, it is closed first. + */ + ~value_type() = default; + + /// Constructor + value_type() = default; + + /// Constructor + value_type(value_type&& other) = default; + + /// Move assignment + value_type& operator=(value_type&& other) = default; + + /// Returns `true` if the file is open + bool + is_open() const + { + return file_.is_open(); + } + + /// Returns the size of the file if open + std::uint64_t + size() const + { + return file_size_; + } + + /// Close the file if open + void + close(); + + /** Open a file at the given path with the specified mode + + @param path The utf-8 encoded path to the file + + @param mode The file mode to use + + @param ec Set to the error, if any occurred + */ + void + open(char const* path, file_mode mode, error_code& ec); + + /** Set the open file + + This function is used to set the open + */ + void + reset(File&& file, error_code& ec); +}; + +template +void +basic_file_body:: +value_type:: +close() +{ + error_code ignored; + file_.close(ignored); +} + +template +void +basic_file_body:: +value_type:: +open(char const* path, file_mode mode, error_code& ec) +{ + // Open the file + file_.open(path, mode, ec); + if(ec) + return; + + // Cache the size + file_size_ = file_.size(ec); + if(ec) + { + close(); + return; + } +} + +template +void +basic_file_body:: +value_type:: +reset(File&& file, error_code& ec) +{ + // First close the file if open + if(file_.is_open()) + { + error_code ignored; + file_.close(ignored); + } + + // Take ownership of the new file + file_ = std::move(file); + + // Cache the size + file_size_ = file_.size(ec); +} + +// This is called from message::payload_size +template +std::uint64_t +basic_file_body:: +size(value_type const& v) +{ + // Forward the call to the body + return v.size(); +} + +//] + +//[example_http_file_body_3 + +template +class basic_file_body::reader +{ + value_type& body_; // The body we are reading from + std::uint64_t remain_; // The number of unread bytes + char buf_[4096]; // Small buffer for reading + +public: + // The type of buffer sequence returned by `get`. + // + using const_buffers_type = + boost::asio::const_buffers_1; + + // Constructor. + // + // `m` holds the message we are sending, which will + // always have the `file_body` as the body type. + // + // Note that the message is passed by non-const reference. + // This is intentional, because reading from the file + // changes its "current position" which counts makes the + // operation logically not-const (although it is bitwise + // const). + // + // The BodyReader concept allows the reader to choose + // whether to take the message by const reference or + // non-const reference. Depending on the choice, a + // serializer constructed using that body type will + // require the same const or non-const reference to + // construct. + // + // Readers which accept const messages usually allow + // the same body to be serialized by multiple threads + // concurrently, while readers accepting non-const + // messages may only be serialized by one thread at + // a time. + // + template + reader(message< + isRequest, basic_file_body, Fields>& m); + + // Initializer + // + // This is called before the body is serialized and + // gives the reader a chance to do something that might + // need to return an error code. + // + void + init(error_code& ec); + + // This function is called zero or more times to + // retrieve buffers. A return value of `boost::none` + // means there are no more buffers. Otherwise, + // the contained pair will have the next buffer + // to serialize, and a `bool` indicating whether + // or not there may be additional buffers. + boost::optional> + get(error_code& ec); +}; + +//] + +//[example_http_file_body_4 + +// Here we just stash a reference to the path for later. +// Rather than dealing with messy constructor exceptions, +// we save the things that might fail for the call to `init`. +// +template +template +basic_file_body:: +reader:: +reader(message& m) + : body_(m.body) +{ + // The file must already be open + BOOST_ASSERT(body_.file_.is_open()); + + // Get the size of the file + remain_ = body_.file_size_; +} + +// Initializer +template +void +basic_file_body:: +reader:: +init(error_code& ec) +{ + // The error_code specification requires that we + // either set the error to some value, or set it + // to indicate no error. + // + // We don't do anything fancy so set "no error" + ec.assign(0, ec.category()); +} + +// This function is called repeatedly by the serializer to +// retrieve the buffers representing the body. Our strategy +// is to read into our buffer and return it until we have +// read through the whole file. +// +template +auto +basic_file_body:: +reader:: +get(error_code& ec) -> + boost::optional> +{ + // Calculate the smaller of our buffer size, + // or the amount of unread data in the file. + auto const amount = remain_ > sizeof(buf_) ? + sizeof(buf_) : static_cast(remain_); + + // Handle the case where the file is zero length + if(amount == 0) + { + // Modify the error code to indicate success + // This is required by the error_code specification. + // + // NOTE We use the existing category instead of calling + // into the library to get the generic category because + // that saves us a possibly expensive atomic operation. + // + ec.assign(0, ec.category()); + return boost::none; + } + + // Now read the next buffer + auto const nread = body_.file_.read(buf_, amount, ec); + if(ec) + return boost::none; + + // Make sure there is forward progress + BOOST_ASSERT(nread != 0); + BOOST_ASSERT(nread <= remain_); + + // Update the amount remaining based on what we got + remain_ -= nread; + + // Return the buffer to the caller. + // + // The second element of the pair indicates whether or + // not there is more data. As long as there is some + // unread bytes, there will be more data. Otherwise, + // we set this bool to `false` so we will not be called + // again. + // + ec.assign(0, ec.category()); + return {{ + const_buffers_type{buf_, nread}, // buffer to return. + remain_ > 0 // `true` if there are more buffers. + }}; +} + +//] + +//[example_http_file_body_5 + +template +class basic_file_body::writer +{ + value_type& body_; // The body we are writing to + +public: + // Constructor. + // + // This is called after the header is parsed and + // indicates that a non-zero sized body may be present. + // `m` holds the message we are receiving, which will + // always have the `file_body` as the body type. + // + template + explicit + writer( + message& m); + + // Initializer + // + // This is called before the body is parsed and + // gives the writer a chance to do something that might + // need to return an error code. It informs us of + // the payload size (`content_length`) which we can + // optionally use for optimization. + // + void + init(boost::optional const&, error_code& ec); + + // This function is called one or more times to store + // buffer sequences corresponding to the incoming body. + // + template + std::size_t + put(ConstBufferSequence const& buffers, + error_code& ec); + + // This function is called when writing is complete. + // It is an opportunity to perform any final actions + // which might fail, in order to return an error code. + // Operations that might fail should not be attemped in + // destructors, since an exception thrown from there + // would terminate the program. + // + void + finish(error_code& ec); +}; + +//] + +//[example_http_file_body_6 + +// We don't do much in the writer constructor since the +// file is already open. +// +template +template +basic_file_body:: +writer:: +writer(message& m) + : body_(m.body) +{ +} + +template +void +basic_file_body:: +writer:: +init( + boost::optional const& content_length, + error_code& ec) +{ + // The file must already be open for writing + BOOST_ASSERT(body_.file_.is_open()); + + // We don't do anything with this but a sophisticated + // application might check available space on the device + // to see if there is enough room to store the body. + boost::ignore_unused(content_length); + + // The error_code specification requires that we + // either set the error to some value, or set it + // to indicate no error. + // + // We don't do anything fancy so set "no error" + ec.assign(0, ec.category()); +} + +// This will get called one or more times with body buffers +// +template +template +std::size_t +basic_file_body:: +writer:: +put(ConstBufferSequence const& buffers, error_code& ec) +{ + // This function must return the total number of + // bytes transferred from the input buffers. + std::size_t nwritten = 0; + + // Loop over all the buffers in the sequence, + // and write each one to the file. + for(boost::asio::const_buffer buffer : buffers) + { + // Write this buffer to the file + nwritten += body_.file_.write( + boost::asio::buffer_cast(buffer), + boost::asio::buffer_size(buffer), + ec); + if(ec) + return nwritten; + } + + // Indicate success + // This is required by the error_code specification + ec.assign(0, ec.category()); + + return nwritten; +} + +// Called after writing is done when there's no error. +template +void +basic_file_body:: +writer:: +finish(error_code& ec) +{ + // This has to be cleared before returning, to + // indicate no error. The specification requires it. + ec.assign(0, ec.category()); +} + +//] + +/// A message body represented by a file on the filesystem. +using file_body = basic_file_body; + +} // http +} // beast + +#include + +#endif diff --git a/include/beast/http/header_parser_v1.hpp b/include/beast/http/header_parser_v1.hpp deleted file mode 100644 index c6f732d21a..0000000000 --- a/include/beast/http/header_parser_v1.hpp +++ /dev/null @@ -1,233 +0,0 @@ -// -// 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_HTTP_HEADERS_PARSER_V1_HPP -#define BEAST_HTTP_HEADERS_PARSER_V1_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -namespace detail { - -struct request_parser_base -{ - std::string method_; - std::string uri_; -}; - -struct response_parser_base -{ - std::string reason_; -}; - -} // detail - -/** A parser for a HTTP/1 request or response header. - - This class uses the HTTP/1 wire format parser to - convert a series of octets into a request or - response @ref header. - - @note A new instance of the parser is required for each message. -*/ -template -class header_parser_v1 - : public basic_parser_v1> - , private std::conditional::type -{ -public: - /// The type of the header this parser produces. - using header_type = header; - -private: - // VFALCO Check Fields requirements? - - std::string field_; - std::string value_; - header_type h_; - bool flush_ = false; - -public: - /// Default constructor - header_parser_v1() = default; - - /// Move constructor - header_parser_v1(header_parser_v1&&) = default; - - /// Copy constructor (disallowed) - header_parser_v1(header_parser_v1 const&) = delete; - - /// Move assignment (disallowed) - header_parser_v1& operator=(header_parser_v1&&) = delete; - - /// Copy assignment (disallowed) - header_parser_v1& operator=(header_parser_v1 const&) = delete; - - /** Construct the parser. - - @param args Forwarded to the header constructor. - */ -#if GENERATING_DOCS - template - explicit - header_parser_v1(Args&&... args); -#else - template::type, header_parser_v1>::value>> - explicit - header_parser_v1(Arg1&& arg1, ArgN&&... argn) - : h_(std::forward(arg1), - std::forward(argn)...) - { - } -#endif - - /** Returns the parsed header - - Only valid if @ref complete would return `true`. - */ - header_type const& - get() const - { - return h_; - } - - /** Returns the parsed header. - - Only valid if @ref complete would return `true`. - */ - header_type& - get() - { - return h_; - } - - /** Returns ownership of the parsed header. - - Ownership is transferred to the caller. Only - valid if @ref complete would return `true`. - - Requires: - @ref header_type is @b MoveConstructible - */ - header_type - release() - { - static_assert(std::is_move_constructible::value, - "MoveConstructible requirements not met"); - return std::move(h_); - } - -private: - friend class basic_parser_v1; - - void flush() - { - if(! flush_) - return; - flush_ = false; - BOOST_ASSERT(! field_.empty()); - h_.fields.insert(field_, value_); - field_.clear(); - value_.clear(); - } - - void on_start(error_code&) - { - } - - void on_method(boost::string_ref const& s, error_code&) - { - this->method_.append(s.data(), s.size()); - } - - void on_uri(boost::string_ref const& s, error_code&) - { - this->uri_.append(s.data(), s.size()); - } - - void on_reason(boost::string_ref const& s, error_code&) - { - this->reason_.append(s.data(), s.size()); - } - - void on_request_or_response(std::true_type) - { - h_.method = std::move(this->method_); - h_.url = std::move(this->uri_); - } - - void on_request_or_response(std::false_type) - { - h_.status = this->status_code(); - h_.reason = std::move(this->reason_); - } - - void on_request(error_code& ec) - { - on_request_or_response( - std::integral_constant{}); - } - - void on_response(error_code& ec) - { - on_request_or_response( - std::integral_constant{}); - } - - void on_field(boost::string_ref const& s, error_code&) - { - flush(); - field_.append(s.data(), s.size()); - } - - void on_value(boost::string_ref const& s, error_code&) - { - value_.append(s.data(), s.size()); - flush_ = true; - } - - void - on_header(std::uint64_t, error_code&) - { - flush(); - h_.version = 10 * this->http_major() + this->http_minor(); - } - - body_what - on_body_what(std::uint64_t, error_code&) - { - return body_what::pause; - } - - void on_body(boost::string_ref const&, error_code&) - { - } - - void on_complete(error_code&) - { - } -}; - -} // http -} // beast - -#endif diff --git a/include/beast/http/impl/basic_fields.ipp b/include/beast/http/impl/basic_fields.ipp deleted file mode 100644 index 069087207c..0000000000 --- a/include/beast/http/impl/basic_fields.ipp +++ /dev/null @@ -1,266 +0,0 @@ -// -// 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_HTTP_IMPL_BASIC_FIELDS_IPP -#define BEAST_HTTP_IMPL_BASIC_FIELDS_IPP - -#include -#include - -namespace beast { -namespace http { - -template -void -basic_fields:: -delete_all() -{ - for(auto it = list_.begin(); it != list_.end();) - { - auto& e = *it++; - alloc_traits::destroy(this->member(), &e); - alloc_traits::deallocate( - this->member(), &e, 1); - } -} - -template -inline -void -basic_fields:: -move_assign(basic_fields& other, std::false_type) -{ - if(this->member() != other.member()) - { - copy_from(other); - other.clear(); - } - else - { - set_ = std::move(other.set_); - list_ = std::move(other.list_); - } -} - -template -inline -void -basic_fields:: -move_assign(basic_fields& other, std::true_type) -{ - this->member() = std::move(other.member()); - set_ = std::move(other.set_); - list_ = std::move(other.list_); -} - -template -inline -void -basic_fields:: -copy_assign(basic_fields const& other, std::false_type) -{ - copy_from(other); -} - -template -inline -void -basic_fields:: -copy_assign(basic_fields const& other, std::true_type) -{ - this->member() = other.member(); - copy_from(other); -} - -//------------------------------------------------------------------------------ - -template -basic_fields:: -~basic_fields() -{ - delete_all(); -} - -template -basic_fields:: -basic_fields(Allocator const& alloc) - : beast::detail::empty_base_optimization< - alloc_type>(alloc) -{ -} - -template -basic_fields:: -basic_fields(basic_fields&& other) - : beast::detail::empty_base_optimization( - std::move(other.member())) - , detail::basic_fields_base( - std::move(other.set_), std::move(other.list_)) -{ -} - -template -auto -basic_fields:: -operator=(basic_fields&& other) -> - basic_fields& -{ - if(this == &other) - return *this; - clear(); - move_assign(other, std::integral_constant{}); - return *this; -} - -template -basic_fields:: -basic_fields(basic_fields const& other) - : basic_fields(alloc_traits:: - select_on_container_copy_construction(other.member())) -{ - copy_from(other); -} - -template -auto -basic_fields:: -operator=(basic_fields const& other) -> - basic_fields& -{ - clear(); - copy_assign(other, std::integral_constant{}); - return *this; -} - -template -template -basic_fields:: -basic_fields(basic_fields const& other) -{ - copy_from(other); -} - -template -template -auto -basic_fields:: -operator=(basic_fields const& other) -> - basic_fields& -{ - clear(); - copy_from(other); - return *this; -} - -template -template -basic_fields:: -basic_fields(FwdIt first, FwdIt last) -{ - for(;first != last; ++first) - insert(first->name(), first->value()); -} - -template -std::size_t -basic_fields:: -count(boost::string_ref const& name) const -{ - auto const it = set_.find(name, less{}); - if(it == set_.end()) - return 0; - auto const last = set_.upper_bound(name, less{}); - return static_cast(std::distance(it, last)); -} - -template -auto -basic_fields:: -find(boost::string_ref const& name) const -> - iterator -{ - auto const it = set_.find(name, less{}); - if(it == set_.end()) - return list_.end(); - return list_.iterator_to(*it); -} - -template -boost::string_ref -basic_fields:: -operator[](boost::string_ref const& name) const -{ - auto const it = find(name); - if(it == end()) - return {}; - return it->second; -} - -template -void -basic_fields:: -clear() noexcept -{ - delete_all(); - list_.clear(); - set_.clear(); -} - -template -std::size_t -basic_fields:: -erase(boost::string_ref const& name) -{ - auto it = set_.find(name, less{}); - if(it == set_.end()) - return 0; - auto const last = set_.upper_bound(name, less{}); - std::size_t n = 1; - for(;;) - { - auto& e = *it++; - set_.erase(set_.iterator_to(e)); - list_.erase(list_.iterator_to(e)); - alloc_traits::destroy(this->member(), &e); - alloc_traits::deallocate(this->member(), &e, 1); - if(it == last) - break; - ++n; - } - return n; -} - -template -void -basic_fields:: -insert(boost::string_ref const& name, - boost::string_ref value) -{ - value = detail::trim(value); - auto const p = alloc_traits::allocate(this->member(), 1); - alloc_traits::construct(this->member(), p, name, value); - set_.insert_before(set_.upper_bound(name, less{}), *p); - list_.push_back(*p); -} - -template -void -basic_fields:: -replace(boost::string_ref const& name, - boost::string_ref value) -{ - value = detail::trim(value); - erase(name); - insert(name, value); -} - -} // http -} // beast - -#endif diff --git a/include/beast/http/impl/basic_parser.ipp b/include/beast/http/impl/basic_parser.ipp new file mode 100644 index 0000000000..9ea02bae0a --- /dev/null +++ b/include/beast/http/impl/basic_parser.ipp @@ -0,0 +1,932 @@ +// +// 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_HTTP_IMPL_BASIC_PARSER_IPP +#define BEAST_HTTP_IMPL_BASIC_PARSER_IPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +template +basic_parser:: +basic_parser() + : body_limit_( + default_body_limit(is_request{})) +{ +} + +template +template +basic_parser:: +basic_parser(basic_parser< + isRequest, OtherDerived>&& other) + : body_limit_(other.body_limit_) + , len_(other.len_) + , buf_(std::move(other.buf_)) + , buf_len_(other.buf_len_) + , skip_(other.skip_) + , state_(other.state_) + , f_(other.f_) +{ +} + +template +bool +basic_parser:: +is_keep_alive() const +{ + BOOST_ASSERT(is_header_done()); + if(f_ & flagHTTP11) + { + if(f_ & flagConnectionClose) + return false; + } + else + { + if(! (f_ & flagConnectionKeepAlive)) + return false; + } + return (f_ & flagNeedEOF) == 0; +} + +template +boost::optional +basic_parser:: +content_length() const +{ + BOOST_ASSERT(is_header_done()); + if(! (f_ & flagContentLength)) + return boost::none; + return len_; +} + +template +void +basic_parser:: +skip(bool v) +{ + BOOST_ASSERT(! got_some()); + if(v) + f_ |= flagSkipBody; + else + f_ &= ~flagSkipBody; +} + +template +template +std::size_t +basic_parser:: +put(ConstBufferSequence const& buffers, + error_code& ec) +{ + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + using boost::asio::buffer_cast; + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + auto const p = buffers.begin(); + auto const last = buffers.end(); + if(p == last) + { + ec.assign(0, ec.category()); + return 0; + } + if(std::next(p) == last) + { + // single buffer + auto const b = *p; + return put(boost::asio::const_buffers_1{ + buffer_cast(b), + buffer_size(b)}, ec); + } + auto const size = buffer_size(buffers); + if(size <= max_stack_buffer) + return put_from_stack(size, buffers, ec); + if(size > buf_len_) + { + // reallocate + buf_ = boost::make_unique_noinit(size); + buf_len_ = size; + } + // flatten + buffer_copy(boost::asio::buffer( + buf_.get(), buf_len_), buffers); + return put(boost::asio::const_buffers_1{ + buf_.get(), buf_len_}, ec); +} + +template +std::size_t +basic_parser:: +put(boost::asio::const_buffers_1 const& buffer, + error_code& ec) +{ + BOOST_ASSERT(state_ != state::complete); + using boost::asio::buffer_size; + auto p = boost::asio::buffer_cast< + char const*>(*buffer.begin()); + auto n = buffer_size(*buffer.begin()); + auto const p0 = p; + auto const p1 = p0 + n; + ec.assign(0, ec.category()); +loop: + switch(state_) + { + case state::nothing_yet: + if(n == 0) + { + ec = error::need_more; + return 0; + } + state_ = state::start_line; + BEAST_FALLTHROUGH; + + case state::start_line: + { + maybe_need_more(p, n, ec); + if(ec) + goto done; + parse_start_line(p, p + std::min( + header_limit_, n), ec, is_request{}); + if(ec) + { + if(ec == error::need_more) + { + if(n >= header_limit_) + { + ec = error::header_limit; + goto done; + } + if(p + 3 <= p1) + skip_ = static_cast< + std::size_t>(p1 - p - 3); + } + goto done; + } + BOOST_ASSERT(! is_done()); + n = static_cast(p1 - p); + if(p >= p1) + { + ec = error::need_more; + goto done; + } + BEAST_FALLTHROUGH; + } + + case state::fields: + maybe_need_more(p, n, ec); + if(ec) + goto done; + parse_fields(p, p + std::min( + header_limit_, n), ec); + if(ec) + { + if(ec == error::need_more) + { + if(n >= header_limit_) + { + ec = error::header_limit; + goto done; + } + if(p + 3 <= p1) + skip_ = static_cast< + std::size_t>(p1 - p - 3); + } + goto done; + } + finish_header(ec, is_request{}); + break; + + case state::body0: + BOOST_ASSERT(! skip_); + impl().on_body(content_length(), ec); + if(ec) + goto done; + state_ = state::body; + BEAST_FALLTHROUGH; + + case state::body: + BOOST_ASSERT(! skip_); + parse_body(p, n, ec); + if(ec) + goto done; + break; + + case state::body_to_eof0: + BOOST_ASSERT(! skip_); + impl().on_body(content_length(), ec); + if(ec) + goto done; + state_ = state::body_to_eof; + BEAST_FALLTHROUGH; + + case state::body_to_eof: + BOOST_ASSERT(! skip_); + parse_body_to_eof(p, n, ec); + if(ec) + goto done; + break; + + case state::chunk_header0: + impl().on_body(content_length(), ec); + if(ec) + goto done; + state_ = state::chunk_header; + BEAST_FALLTHROUGH; + + case state::chunk_header: + parse_chunk_header(p, n, ec); + if(ec) + goto done; + break; + + case state::chunk_body: + parse_chunk_body(p, n, ec); + if(ec) + goto done; + break; + + case state::complete: + ec.assign(0, ec.category()); + goto done; + } + if(p < p1 && ! is_done() && eager()) + { + n = static_cast(p1 - p); + goto loop; + } +done: + return static_cast(p - p0); +} + +template +void +basic_parser:: +put_eof(error_code& ec) +{ + BOOST_ASSERT(got_some()); + if( state_ == state::start_line || + state_ == state::fields) + { + ec = error::partial_message; + return; + } + if(f_ & (flagContentLength | flagChunked)) + { + if(state_ != state::complete) + { + ec = error::partial_message; + return; + } + ec.assign(0, ec.category()); + return; + } + impl().on_complete(ec); + if(ec) + return; + state_ = state::complete; +} + +template +template +std::size_t +basic_parser:: +put_from_stack(std::size_t size, + ConstBufferSequence const& buffers, + error_code& ec) +{ + char buf[max_stack_buffer]; + using boost::asio::buffer; + using boost::asio::buffer_copy; + buffer_copy(buffer(buf, sizeof(buf)), buffers); + return put(boost::asio::const_buffers_1{ + buf, size}, ec); +} + +template +inline +void +basic_parser:: +maybe_need_more( + char const* p, std::size_t n, + error_code& ec) +{ + if(skip_ == 0) + return; + if( n > header_limit_) + n = header_limit_; + if(n < skip_ + 4) + { + ec = error::need_more; + return; + } + auto const term = + find_eom(p + skip_, p + n); + if(! term) + { + skip_ = n - 3; + if(skip_ + 4 > header_limit_) + { + ec = error::header_limit; + return; + } + ec = error::need_more; + return; + } + skip_ = 0; +} + +template +inline +void +basic_parser:: +parse_start_line( + char const*& in, char const* last, + error_code& ec, std::true_type) +{ +/* + request-line = method SP request-target SP HTTP-version CRLF + method = token +*/ + auto p = in; + + string_view method; + parse_method(p, last, method, ec); + if(ec) + return; + + string_view target; + parse_target(p, last, target, ec); + if(ec) + return; + + int version = 0; + parse_version(p, last, version, ec); + if(ec) + return; + if(version < 10 || version > 11) + { + ec = error::bad_version; + return; + } + + if(p + 2 > last) + { + ec = error::need_more; + return; + } + if(p[0] != '\r' || p[1] != '\n') + { + ec = error::bad_version; + return; + } + p += 2; + + if(version >= 11) + f_ |= flagHTTP11; + + impl().on_request(string_to_verb(method), + method, target, version, ec); + if(ec) + return; + + in = p; + state_ = state::fields; +} + +template +inline +void +basic_parser:: +parse_start_line( + char const*& in, char const* last, + error_code& ec, std::false_type) +{ +/* + status-line = HTTP-version SP status-code SP reason-phrase CRLF + status-code = 3*DIGIT + reason-phrase = *( HTAB / SP / VCHAR / obs-text ) +*/ + auto p = in; + + int version = 0; + parse_version(p, last, version, ec); + if(ec) + return; + if(version < 10 || version > 11) + { + ec = error::bad_version; + return; + } + + // SP + if(p + 1 > last) + { + ec = error::need_more; + return; + } + if(*p++ != ' ') + { + ec = error::bad_version; + return; + } + + parse_status(p, last, status_, ec); + if(ec) + return; + + // parse reason CRLF + string_view reason; + parse_reason(p, last, reason, ec); + if(ec) + return; + + if(version >= 11) + f_ |= flagHTTP11; + + impl().on_response( + status_, reason, version, ec); + if(ec) + return; + + in = p; + state_ = state::fields; +} + +template +void +basic_parser:: +parse_fields(char const*& in, + char const* last, error_code& ec) +{ + string_view name; + string_view value; + // https://stackoverflow.com/questions/686217/maximum-on-http-header-values + static_string buf; + auto p = in; + for(;;) + { + if(p + 2 > last) + { + ec = error::need_more; + return; + } + if(p[0] == '\r') + { + if(p[1] != '\n') + ec = error::bad_line_ending; + in = p + 2; + return; + } + parse_field(p, last, name, value, buf, ec); + if(ec) + return; + auto const f = string_to_field(name); + do_field(f, value, ec); + if(ec) + return; + impl().on_field(f, name, value, ec); + if(ec) + return; + in = p; + } +} + +template +inline +void +basic_parser:: +finish_header(error_code& ec, std::true_type) +{ + // RFC 7230 section 3.3 + // https://tools.ietf.org/html/rfc7230#section-3.3 + + if(f_ & flagSkipBody) + { + state_ = state::complete; + } + else if(f_ & flagContentLength) + { + if(len_ > 0) + { + f_ |= flagHasBody; + state_ = state::body0; + } + else + { + state_ = state::complete; + } + } + else if(f_ & flagChunked) + { + f_ |= flagHasBody; + state_ = state::chunk_header0; + } + else + { + len_ = 0; + state_ = state::complete; + } + + impl().on_header(ec); + if(ec) + return; + if(state_ == state::complete) + { + impl().on_complete(ec); + if(ec) + return; + } +} + +template +inline +void +basic_parser:: +finish_header(error_code& ec, std::false_type) +{ + // RFC 7230 section 3.3 + // https://tools.ietf.org/html/rfc7230#section-3.3 + + if( (f_ & flagSkipBody) || // e.g. response to a HEAD request + status_ / 100 == 1 || // 1xx e.g. Continue + status_ == 204 || // No Content + status_ == 304) // Not Modified + { + state_ = state::complete; + return; + } + + if(f_ & flagContentLength) + { + if(len_ > 0) + { + f_ |= flagHasBody; + state_ = state::body0; + } + else + { + state_ = state::complete; + } + } + else if(f_ & flagChunked) + { + f_ |= flagHasBody; + state_ = state::chunk_header0; + } + else + { + f_ |= flagHasBody; + f_ |= flagNeedEOF; + state_ = state::body_to_eof0; + } + + impl().on_header(ec); + if(ec) + return; + if(state_ == state::complete) + { + impl().on_complete(ec); + if(ec) + return; + } +} + +template +inline +void +basic_parser:: +parse_body(char const*& p, + std::size_t n, error_code& ec) +{ + n = impl().on_data(string_view{p, + beast::detail::clamp(len_, n)}, ec); + p += n; + len_ -= n; + if(ec) + return; + if(len_ > 0) + return; + impl().on_complete(ec); + if(ec) + return; + state_ = state::complete; +} + +template +inline +void +basic_parser:: +parse_body_to_eof(char const*& p, + std::size_t n, error_code& ec) +{ + if(n > body_limit_) + { + ec = error::body_limit; + return; + } + body_limit_ = body_limit_ - n; + n = impl().on_data(string_view{p, n}, ec); + p += n; + if(ec) + return; +} + +template +void +basic_parser:: +parse_chunk_header(char const*& p0, + std::size_t n, error_code& ec) +{ +/* + chunked-body = *chunk last-chunk trailer-part CRLF + + chunk = chunk-size [ chunk-ext ] CRLF chunk-data CRLF + last-chunk = 1*("0") [ chunk-ext ] CRLF + trailer-part = *( header-field CRLF ) + + chunk-size = 1*HEXDIG + chunk-data = 1*OCTET ; a sequence of chunk-size octets + chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) + chunk-ext-name = token + chunk-ext-val = token / quoted-string +*/ + + auto p = p0; + auto const pend = p + n; + char const* eol; + + if(! (f_ & flagFinalChunk)) + { + if(n < skip_ + 2) + { + ec = error::need_more; + return; + } + if(f_ & flagExpectCRLF) + { + // Treat the last CRLF in a chunk as + // part of the next chunk, so p can + // be parsed in one call instead of two. + if(! parse_crlf(p)) + { + ec = error::bad_chunk; + return; + } + } + eol = find_eol(p0 + skip_, pend, ec); + if(ec) + return; + if(! eol) + { + ec = error::need_more; + skip_ = n - 1; + return; + } + skip_ = static_cast< + std::size_t>(eol - 2 - p0); + + std::uint64_t v; + if(! parse_hex(p, v)) + { + ec = error::bad_chunk; + return; + } + if(v != 0) + { + if(v > body_limit_) + { + ec = error::body_limit; + return; + } + body_limit_ -= v; + if(*p == ';') + { + // VFALCO TODO Validate extension + impl().on_chunk(v, make_string( + p, eol - 2), ec); + if(ec) + return; + } + else if(p == eol - 2) + { + impl().on_chunk(v, {}, ec); + if(ec) + return; + } + else + { + ec = error::bad_chunk; + return; + } + len_ = v; + skip_ = 2; + p0 = eol; + f_ |= flagExpectCRLF; + state_ = state::chunk_body; + return; + } + + f_ |= flagFinalChunk; + } + else + { + BOOST_ASSERT(n >= 5); + if(f_ & flagExpectCRLF) + BOOST_VERIFY(parse_crlf(p)); + std::uint64_t v; + BOOST_VERIFY(parse_hex(p, v)); + eol = find_eol(p, pend, ec); + BOOST_ASSERT(! ec); + } + + auto eom = find_eom(p0 + skip_, pend); + if(! eom) + { + BOOST_ASSERT(n >= 3); + skip_ = n - 3; + ec = error::need_more; + return; + } + + if(*p == ';') + { + // VFALCO TODO Validate extension + impl().on_chunk(0, make_string( + p, eol - 2), ec); + if(ec) + return; + } + p = eol; + parse_fields(p, eom, ec); + if(ec) + return; + BOOST_ASSERT(p == eom); + p0 = eom; + + impl().on_complete(ec); + if(ec) + return; + state_ = state::complete; +} + +template +inline +void +basic_parser:: +parse_chunk_body(char const*& p, + std::size_t n, error_code& ec) +{ + n = impl().on_data(string_view{p, + beast::detail::clamp(len_, n)}, ec); + p += n; + len_ -= n; + if(ec) + return; + if(len_ > 0) + return; + state_ = state::chunk_header; +} + +template +void +basic_parser:: +do_field(field f, + string_view value, error_code& ec) +{ + // Connection + if(f == field::connection || + f == field::proxy_connection) + { + auto const list = opt_token_list{value}; + if(! validate_list(list)) + { + // VFALCO Should this be a field specific error? + ec = error::bad_value; + return; + } + for(auto const& s : list) + { + if(iequals({"close", 5}, s)) + { + f_ |= flagConnectionClose; + continue; + } + + if(iequals({"keep-alive", 10}, s)) + { + f_ |= flagConnectionKeepAlive; + continue; + } + + if(iequals({"upgrade", 7}, s)) + { + f_ |= flagConnectionUpgrade; + continue; + } + } + ec.assign(0, ec.category()); + return; + } + + // Content-Length + if(f == field::content_length) + { + if(f_ & flagContentLength) + { + // duplicate + ec = error::bad_content_length; + return; + } + + if(f_ & flagChunked) + { + // conflicting field + ec = error::bad_content_length; + return; + } + + std::uint64_t v; + if(! parse_dec( + value.begin(), value.end(), v)) + { + ec = error::bad_content_length; + return; + } + + if(v > body_limit_) + { + ec = error::body_limit; + return; + } + + ec.assign(0, ec.category()); + len_ = v; + f_ |= flagContentLength; + return; + } + + // Transfer-Encoding + if(f == field::transfer_encoding) + { + if(f_ & flagChunked) + { + // duplicate + ec = error::bad_transfer_encoding; + return; + } + + if(f_ & flagContentLength) + { + // conflicting field + ec = error::bad_transfer_encoding; + return; + } + + ec.assign(0, ec.category()); + auto const v = token_list{value}; + auto const p = std::find_if(v.begin(), v.end(), + [&](typename token_list::value_type const& s) + { + return iequals({"chunked", 7}, s); + }); + if(p == v.end()) + return; + if(std::next(p) != v.end()) + return; + len_ = 0; + f_ |= flagChunked; + return; + } + + // Upgrade + if(f == field::upgrade) + { + ec.assign(0, ec.category()); + f_ |= flagUpgrade; + return; + } + + ec.assign(0, ec.category()); +} + +} // http +} // beast + +#endif diff --git a/include/beast/http/impl/basic_parser_v1.ipp b/include/beast/http/impl/basic_parser_v1.ipp deleted file mode 100644 index 9b1be47dfc..0000000000 --- a/include/beast/http/impl/basic_parser_v1.ipp +++ /dev/null @@ -1,1289 +0,0 @@ -// -// 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_HTTP_IMPL_BASIC_PARSER_V1_IPP -#define BEAST_HTTP_IMPL_BASIC_PARSER_V1_IPP - -#include -#include -#include - -namespace beast { -namespace http { - -/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev - * - * Additional changes are licensed under the same terms as NGINX and - * copyright Joyent, Inc. and other Node contributors. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ -/* This code is a modified version of nodejs/http-parser, copyright above: - https://github.com/nodejs/http-parser -*/ - -template -basic_parser_v1:: -basic_parser_v1() - : flags_(0) -{ - init(); -} - -template -template -basic_parser_v1:: -basic_parser_v1(basic_parser_v1< - isRequest, OtherDerived> const& other) - : h_max_(other.h_max_) - , h_left_(other.h_left_) - , b_max_(other.b_max_) - , b_left_(other.b_left_) - , content_length_(other.content_length_) - , cb_(nullptr) - , s_(other.s_) - , fs_(other.fs_) - , pos_(other.pos_) - , http_major_(other.http_major_) - , http_minor_(other.http_minor_) - , status_code_(other.status_code_) - , flags_(other.flags_) - , upgrade_(other.upgrade_) -{ - BOOST_ASSERT(! other.cb_); -} - -template -template -auto -basic_parser_v1:: -operator=(basic_parser_v1< - isRequest, OtherDerived> const& other) -> - basic_parser_v1& -{ - BOOST_ASSERT(! other.cb_); - h_max_ = other.h_max_; - h_left_ = other.h_left_; - b_max_ = other.b_max_; - b_left_ = other.b_left_; - content_length_ = other.content_length_; - cb_ = nullptr; - s_ = other.s_; - fs_ = other.fs_; - pos_ = other.pos_; - http_major_ = other.http_major_; - http_minor_ = other.http_minor_; - status_code_ = other.status_code_; - flags_ = other.flags_; - upgrade_ = other.upgrade_; - flags_ &= ~parse_flag::paused; - return *this; -} - -template -bool -basic_parser_v1:: -keep_alive() const -{ - if(http_major_ >= 1 && http_minor_ >= 1) - { - if(flags_ & parse_flag::connection_close) - return false; - } - else - { - if(! (flags_ & parse_flag::connection_keep_alive)) - return false; - } - return ! needs_eof(); -} - -template -template -typename std::enable_if< - ! std::is_convertible::value, - std::size_t>::type -basic_parser_v1:: -write(ConstBufferSequence const& buffers, error_code& ec) -{ - static_assert(is_ConstBufferSequence::value, - "ConstBufferSequence requirements not met"); - std::size_t used = 0; - for(auto const& buffer : buffers) - { - used += write(buffer, ec); - if(ec) - break; - } - return used; -} - -template -std::size_t -basic_parser_v1:: -write(boost::asio::const_buffer const& buffer, error_code& ec) -{ - using beast::http::detail::is_digit; - using beast::http::detail::is_tchar; - using beast::http::detail::is_text; - using beast::http::detail::to_field_char; - using beast::http::detail::to_value_char; - using beast::http::detail::unhex; - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - - auto const data = buffer_cast(buffer); - auto const size = buffer_size(buffer); - - if(size == 0 && s_ != s_dead) - return 0; - - auto begin = - reinterpret_cast(data); - auto const end = begin + size; - auto p = begin; - auto used = [&] - { - return p - reinterpret_cast(data); - }; - auto err = [&](parse_error ev) - { - ec = ev; - s_ = s_dead; - return used(); - }; - auto errc = [&] - { - s_ = s_dead; - return used(); - }; - auto piece = [&] - { - return boost::string_ref{ - begin, static_cast(p - begin)}; - }; - auto cb = [&](pmf_t next) - { - if(cb_ && p != begin) - { - (this->*cb_)(ec, piece()); - if(ec) - return true; // error - } - cb_ = next; - if(cb_) - begin = p; - return false; - }; - for(;p != end; ++p) - { - unsigned char ch = *p; - redo: - switch(s_) - { - case s_dead: - case s_closed_complete: - return err(parse_error::connection_closed); - break; - - case s_req_start: - flags_ = 0; - cb_ = nullptr; - content_length_ = no_content_length; - s_ = s_req_method0; - goto redo; - - case s_req_method0: - if(! is_tchar(ch)) - return err(parse_error::bad_method); - call_on_start(ec); - if(ec) - return errc(); - BOOST_ASSERT(! cb_); - cb(&self::call_on_method); - s_ = s_req_method; - break; - - case s_req_method: - if(ch == ' ') - { - if(cb(nullptr)) - return errc(); - s_ = s_req_url0; - break; - } - if(! is_tchar(ch)) - return err(parse_error::bad_method); - break; - - case s_req_url0: - { - if(ch == ' ') - return err(parse_error::bad_uri); - // VFALCO TODO Better checking for valid URL characters - if(! is_text(ch)) - return err(parse_error::bad_uri); - BOOST_ASSERT(! cb_); - cb(&self::call_on_uri); - s_ = s_req_url; - break; - } - - case s_req_url: - if(ch == ' ') - { - if(cb(nullptr)) - return errc(); - s_ = s_req_http; - break; - } - // VFALCO TODO Better checking for valid URL characters - if(! is_text(ch)) - return err(parse_error::bad_uri); - break; - - case s_req_http: - if(ch != 'H') - return err(parse_error::bad_version); - s_ = s_req_http_H; - break; - - case s_req_http_H: - if(ch != 'T') - return err(parse_error::bad_version); - s_ = s_req_http_HT; - break; - - case s_req_http_HT: - if(ch != 'T') - return err(parse_error::bad_version); - s_ = s_req_http_HTT; - break; - - case s_req_http_HTT: - if(ch != 'P') - return err(parse_error::bad_version); - s_ = s_req_http_HTTP; - break; - - case s_req_http_HTTP: - if(ch != '/') - return err(parse_error::bad_version); - s_ = s_req_major; - break; - - case s_req_major: - if(! is_digit(ch)) - return err(parse_error::bad_version); - http_major_ = ch - '0'; - s_ = s_req_dot; - break; - - case s_req_dot: - if(ch != '.') - return err(parse_error::bad_version); - s_ = s_req_minor; - break; - - case s_req_minor: - if(! is_digit(ch)) - return err(parse_error::bad_version); - http_minor_ = ch - '0'; - s_ = s_req_cr; - break; - - case s_req_cr: - if(ch != '\r') - return err(parse_error::bad_version); - s_ = s_req_lf; - break; - - case s_req_lf: - if(ch != '\n') - return err(parse_error::bad_crlf); - call_on_request(ec); - if(ec) - return errc(); - s_ = s_header_name0; - break; - - //---------------------------------------------------------------------- - - case s_res_start: - flags_ = 0; - cb_ = nullptr; - content_length_ = no_content_length; - if(ch != 'H') - return err(parse_error::bad_version); - call_on_start(ec); - if(ec) - return errc(); - s_ = s_res_H; - break; - - case s_res_H: - if(ch != 'T') - return err(parse_error::bad_version); - s_ = s_res_HT; - break; - - case s_res_HT: - if(ch != 'T') - return err(parse_error::bad_version); - s_ = s_res_HTT; - break; - - case s_res_HTT: - if(ch != 'P') - return err(parse_error::bad_version); - s_ = s_res_HTTP; - break; - - case s_res_HTTP: - if(ch != '/') - return err(parse_error::bad_version); - s_ = s_res_major; - break; - - case s_res_major: - if(! is_digit(ch)) - return err(parse_error::bad_version); - http_major_ = ch - '0'; - s_ = s_res_dot; - break; - - case s_res_dot: - if(ch != '.') - return err(parse_error::bad_version); - s_ = s_res_minor; - break; - - case s_res_minor: - if(! is_digit(ch)) - return err(parse_error::bad_version); - http_minor_ = ch - '0'; - s_ = s_res_space_1; - break; - - case s_res_space_1: - if(ch != ' ') - return err(parse_error::bad_version); - s_ = s_res_status0; - break; - - case s_res_status0: - if(! is_digit(ch)) - return err(parse_error::bad_status); - status_code_ = ch - '0'; - s_ = s_res_status1; - break; - - case s_res_status1: - if(! is_digit(ch)) - return err(parse_error::bad_status); - status_code_ = status_code_ * 10 + ch - '0'; - s_ = s_res_status2; - break; - - case s_res_status2: - if(! is_digit(ch)) - return err(parse_error::bad_status); - status_code_ = status_code_ * 10 + ch - '0'; - s_ = s_res_space_2; - break; - - case s_res_space_2: - if(ch != ' ') - return err(parse_error::bad_status); - s_ = s_res_reason0; - break; - - case s_res_reason0: - if(ch == '\r') - { - s_ = s_res_line_lf; - break; - } - if(! is_text(ch)) - return err(parse_error::bad_reason); - BOOST_ASSERT(! cb_); - cb(&self::call_on_reason); - s_ = s_res_reason; - break; - - case s_res_reason: - if(ch == '\r') - { - if(cb(nullptr)) - return errc(); - s_ = s_res_line_lf; - break; - } - if(! is_text(ch)) - return err(parse_error::bad_reason); - break; - - case s_res_line_lf: - if(ch != '\n') - return err(parse_error::bad_crlf); - s_ = s_res_line_done; - break; - - case s_res_line_done: - call_on_response(ec); - if(ec) - return errc(); - s_ = s_header_name0; - goto redo; - - //---------------------------------------------------------------------- - - case s_header_name0: - { - if(ch == '\r') - { - s_ = s_headers_almost_done; - break; - } - auto c = to_field_char(ch); - if(! c) - return err(parse_error::bad_field); - switch(c) - { - case 'c': pos_ = 0; fs_ = h_C; break; - case 'p': pos_ = 0; fs_ = h_matching_proxy_connection; break; - case 't': pos_ = 0; fs_ = h_matching_transfer_encoding; break; - case 'u': pos_ = 0; fs_ = h_matching_upgrade; break; - default: - fs_ = h_general; - break; - } - BOOST_ASSERT(! cb_); - cb(&self::call_on_field); - s_ = s_header_name; - break; - } - - case s_header_name: - { - for(; p != end; ++p) - { - ch = *p; - auto c = to_field_char(ch); - if(! c) - break; - switch(fs_) - { - default: - case h_general: - break; - case h_C: ++pos_; fs_ = c=='o' ? h_CO : h_general; break; - case h_CO: ++pos_; fs_ = c=='n' ? h_CON : h_general; break; - case h_CON: - ++pos_; - switch(c) - { - case 'n': fs_ = h_matching_connection; break; - case 't': fs_ = h_matching_content_length; break; - default: - fs_ = h_general; - } - break; - - case h_matching_connection: - ++pos_; - if(c != detail::parser_str::connection[pos_]) - fs_ = h_general; - else if(pos_ == sizeof(detail::parser_str::connection)-2) - fs_ = h_connection; - break; - - case h_matching_proxy_connection: - ++pos_; - if(c != detail::parser_str::proxy_connection[pos_]) - fs_ = h_general; - else if(pos_ == sizeof(detail::parser_str::proxy_connection)-2) - fs_ = h_connection; - break; - - case h_matching_content_length: - ++pos_; - if(c != detail::parser_str::content_length[pos_]) - fs_ = h_general; - else if(pos_ == sizeof(detail::parser_str::content_length)-2) - { - if(flags_ & parse_flag::contentlength) - return err(parse_error::bad_content_length); - fs_ = h_content_length0; - } - break; - - case h_matching_transfer_encoding: - ++pos_; - if(c != detail::parser_str::transfer_encoding[pos_]) - fs_ = h_general; - else if(pos_ == sizeof(detail::parser_str::transfer_encoding)-2) - fs_ = h_transfer_encoding; - break; - - case h_matching_upgrade: - ++pos_; - if(c != detail::parser_str::upgrade[pos_]) - fs_ = h_general; - else if(pos_ == sizeof(detail::parser_str::upgrade)-2) - fs_ = h_upgrade; - break; - - case h_connection: - case h_content_length0: - case h_transfer_encoding: - case h_upgrade: - fs_ = h_general; - break; - } - } - if(p == end) - { - --p; - break; - } - if(ch == ':') - { - if(cb(nullptr)) - return errc(); - s_ = s_header_value0; - break; - } - return err(parse_error::bad_field); - } - /* - header-field = field-name ":" OWS field-value OWS - field-name = token - field-value = *( field-content / obs-fold ) - field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] - field-vchar = VCHAR / obs-text - obs-fold = CRLF 1*( SP / HTAB ) - ; obsolete line folding - */ - case s_header_value0: - if(ch == ' ' || ch == '\t') - break; - if(ch == '\r') - { - s_ = s_header_value0_lf; - break; - } - if(fs_ == h_content_length0) - { - content_length_ = 0; - flags_ |= parse_flag::contentlength; - } - BOOST_ASSERT(! cb_); - cb(&self::call_on_value); - s_ = s_header_value; - // fall through - - case s_header_value: - { - for(; p != end; ++p) - { - ch = *p; - if(ch == '\r') - { - if(cb(nullptr)) - return errc(); - s_ = s_header_value_lf; - break; - } - auto const c = to_value_char(ch); - if(! c) - return err(parse_error::bad_value); - switch(fs_) - { - case h_general: - default: - break; - - case h_connection: - switch(c) - { - case 'k': - pos_ = 0; - fs_ = h_matching_connection_keep_alive; - break; - case 'c': - pos_ = 0; - fs_ = h_matching_connection_close; - break; - case 'u': - pos_ = 0; - fs_ = h_matching_connection_upgrade; - break; - default: - if(ch == ' ' || ch == '\t' || ch == ',') - break; - if(! is_tchar(ch)) - return err(parse_error::bad_value); - fs_ = h_connection_token; - break; - } - break; - - case h_matching_connection_keep_alive: - ++pos_; - if(c != detail::parser_str::keep_alive[pos_]) - fs_ = h_connection_token; - else if(pos_ == sizeof(detail::parser_str::keep_alive)- 2) - fs_ = h_connection_keep_alive; - break; - - case h_matching_connection_close: - ++pos_; - if(c != detail::parser_str::close[pos_]) - fs_ = h_connection_token; - else if(pos_ == sizeof(detail::parser_str::close)-2) - fs_ = h_connection_close; - break; - - case h_matching_connection_upgrade: - ++pos_; - if(c != detail::parser_str::upgrade[pos_]) - fs_ = h_connection_token; - else if(pos_ == sizeof(detail::parser_str::upgrade)-2) - fs_ = h_connection_upgrade; - break; - - case h_connection_close: - if(ch == ',') - { - fs_ = h_connection; - flags_ |= parse_flag::connection_close; - } - else if(ch == ' ' || ch == '\t') - fs_ = h_connection_close_ows; - else if(is_tchar(ch)) - fs_ = h_connection_token; - else - return err(parse_error::bad_value); - break; - - case h_connection_close_ows: - if(ch == ',') - { - fs_ = h_connection; - flags_ |= parse_flag::connection_close; - break; - } - if(ch == ' ' || ch == '\t') - break; - return err(parse_error::bad_value); - - case h_connection_keep_alive: - if(ch == ',') - { - fs_ = h_connection; - flags_ |= parse_flag::connection_keep_alive; - } - else if(ch == ' ' || ch == '\t') - fs_ = h_connection_keep_alive_ows; - else if(is_tchar(ch)) - fs_ = h_connection_token; - else - return err(parse_error::bad_value); - break; - - case h_connection_keep_alive_ows: - if(ch == ',') - { - fs_ = h_connection; - flags_ |= parse_flag::connection_keep_alive; - break; - } - if(ch == ' ' || ch == '\t') - break; - return err(parse_error::bad_value); - - case h_connection_upgrade: - if(ch == ',') - { - fs_ = h_connection; - flags_ |= parse_flag::connection_upgrade; - } - else if(ch == ' ' || ch == '\t') - fs_ = h_connection_upgrade_ows; - else if(is_tchar(ch)) - fs_ = h_connection_token; - else - return err(parse_error::bad_value); - break; - - case h_connection_upgrade_ows: - if(ch == ',') - { - fs_ = h_connection; - flags_ |= parse_flag::connection_upgrade; - break; - } - if(ch == ' ' || ch == '\t') - break; - return err(parse_error::bad_value); - - case h_connection_token: - if(ch == ',') - fs_ = h_connection; - else if(ch == ' ' || ch == '\t') - fs_ = h_connection_token_ows; - else if(! is_tchar(ch)) - return err(parse_error::bad_value); - break; - - case h_connection_token_ows: - if(ch == ',') - { - fs_ = h_connection; - break; - } - if(ch == ' ' || ch == '\t') - break; - return err(parse_error::bad_value); - - case h_content_length0: - if(! is_digit(ch)) - return err(parse_error::bad_content_length); - content_length_ = ch - '0'; - fs_ = h_content_length; - break; - - case h_content_length: - if(ch == ' ' || ch == '\t') - { - fs_ = h_content_length_ows; - break; - } - if(! is_digit(ch)) - return err(parse_error::bad_content_length); - if(content_length_ > (no_content_length - 10) / 10) - return err(parse_error::bad_content_length); - content_length_ = - content_length_ * 10 + ch - '0'; - break; - - case h_content_length_ows: - if(ch != ' ' && ch != '\t') - return err(parse_error::bad_content_length); - break; - - case h_transfer_encoding: - if(c == 'c') - { - pos_ = 0; - fs_ = h_matching_transfer_encoding_chunked; - } - else if(c != ' ' && c != '\t' && c != ',') - { - fs_ = h_matching_transfer_encoding_general; - } - break; - - case h_matching_transfer_encoding_chunked: - ++pos_; - if(c != detail::parser_str::chunked[pos_]) - fs_ = h_matching_transfer_encoding_general; - else if(pos_ == sizeof(detail::parser_str::chunked)-2) - fs_ = h_transfer_encoding_chunked; - break; - - case h_matching_transfer_encoding_general: - if(c == ',') - fs_ = h_transfer_encoding; - break; - - case h_transfer_encoding_chunked: - if(c != ' ' && c != '\t' && c != ',') - fs_ = h_transfer_encoding; - break; - - case h_upgrade: - flags_ |= parse_flag::upgrade; - fs_ = h_general; - break; - } - } - if(p == end) - --p; - break; - } - - case s_header_value0_lf: - if(ch != '\n') - return err(parse_error::bad_crlf); - s_ = s_header_value0_almost_done; - break; - - case s_header_value0_almost_done: - if(ch == ' ' || ch == '\t') - { - s_ = s_header_value0; - break; - } - if(fs_ == h_content_length0) - return err(parse_error::bad_content_length); - if(fs_ == h_upgrade) - flags_ |= parse_flag::upgrade; - BOOST_ASSERT(! cb_); - call_on_value(ec, boost::string_ref{"", 0}); - if(ec) - return errc(); - s_ = s_header_name0; - goto redo; - - case s_header_value_lf: - if(ch != '\n') - return err(parse_error::bad_crlf); - s_ = s_header_value_almost_done; - break; - - case s_header_value_almost_done: - if(ch == ' ' || ch == '\t') - { - switch(fs_) - { - case h_matching_connection_keep_alive: - case h_matching_connection_close: - case h_matching_connection_upgrade: - fs_ = h_connection_token_ows; - break; - - case h_connection_close: - fs_ = h_connection_close_ows; - break; - - case h_connection_keep_alive: - fs_ = h_connection_keep_alive_ows; - break; - - case h_connection_upgrade: - fs_ = h_connection_upgrade_ows; - break; - - case h_content_length: - fs_ = h_content_length_ows; - break; - - case h_matching_transfer_encoding_chunked: - fs_ = h_matching_transfer_encoding_general; - break; - - default: - break; - } - call_on_value(ec, boost::string_ref(" ", 1)); - s_ = s_header_value_unfold; - break; - } - switch(fs_) - { - case h_connection_keep_alive: - case h_connection_keep_alive_ows: - flags_ |= parse_flag::connection_keep_alive; - break; - case h_connection_close: - case h_connection_close_ows: - flags_ |= parse_flag::connection_close; - break; - - case h_connection_upgrade: - case h_connection_upgrade_ows: - flags_ |= parse_flag::connection_upgrade; - break; - - case h_transfer_encoding_chunked: - case h_transfer_encoding_chunked_ows: - flags_ |= parse_flag::chunked; - break; - - default: - break; - } - s_ = s_header_name0; - goto redo; - - case s_header_value_unfold: - BOOST_ASSERT(! cb_); - cb(&self::call_on_value); - s_ = s_header_value; - goto redo; - - case s_headers_almost_done: - { - if(ch != '\n') - return err(parse_error::bad_crlf); - if(flags_ & parse_flag::trailing) - { - //if(cb(&self::call_on_chunk_complete)) return errc(); - s_ = s_complete; - goto redo; - } - if((flags_ & parse_flag::chunked) && (flags_ & parse_flag::contentlength)) - return err(parse_error::illegal_content_length); - upgrade_ = ((flags_ & (parse_flag::upgrade | parse_flag::connection_upgrade)) == - (parse_flag::upgrade | parse_flag::connection_upgrade)) /*|| method == "connect"*/; - call_on_headers(ec); - if(ec) - return errc(); - auto const what = call_on_body_what(ec); - if(ec) - return errc(); - switch(what) - { - case body_what::normal: - break; - case body_what::upgrade: - upgrade_ = true; - // fall through - case body_what::skip: - flags_ |= parse_flag::skipbody; - break; - case body_what::pause: - ++p; - s_ = s_body_pause; - flags_ |= parse_flag::paused; - return used(); - } - s_ = s_headers_done; - goto redo; - } - - case s_body_pause: - { - auto const what = call_on_body_what(ec); - if(ec) - return errc(); - switch(what) - { - case body_what::normal: - break; - case body_what::upgrade: - upgrade_ = true; - // fall through - case body_what::skip: - flags_ |= parse_flag::skipbody; - break; - case body_what::pause: - return used(); - } - --p; - s_ = s_headers_done; - // fall through - } - - case s_headers_done: - { - BOOST_ASSERT(! cb_); - if(ec) - return errc(); - bool const hasBody = - (flags_ & parse_flag::chunked) || (content_length_ > 0 && - content_length_ != no_content_length); - if(upgrade_ && (/*method == "connect" ||*/ (flags_ & parse_flag::skipbody) || ! hasBody)) - { - s_ = s_complete; - } - else if((flags_ & parse_flag::skipbody) || content_length_ == 0) - { - s_ = s_complete; - } - else if(flags_ & parse_flag::chunked) - { - s_ = s_chunk_size0; - break; - } - else if(content_length_ != no_content_length) - { - s_ = s_body_identity0; - break; - } - else if(! needs_eof()) - { - s_ = s_complete; - } - else - { - s_ = s_body_identity_eof0; - break; - } - goto redo; - } - - case s_body_identity0: - BOOST_ASSERT(! cb_); - cb(&self::call_on_body); - s_ = s_body_identity; - // fall through - - case s_body_identity: - { - std::size_t n; - if(static_cast((end - p)) < content_length_) - n = end - p; - else - n = static_cast(content_length_); - BOOST_ASSERT(content_length_ != 0 && content_length_ != no_content_length); - content_length_ -= n; - if(content_length_ == 0) - { - p += n - 1; - s_ = s_complete; - goto redo; // ???? - } - p += n - 1; - break; - } - - case s_body_identity_eof0: - BOOST_ASSERT(! cb_); - cb(&self::call_on_body); - s_ = s_body_identity_eof; - // fall through - - case s_body_identity_eof: - p = end - 1; - break; - - case s_chunk_size0: - { - auto v = unhex(ch); - if(v == -1) - return err(parse_error::invalid_chunk_size); - content_length_ = v; - s_ = s_chunk_size; - break; - } - - case s_chunk_size: - { - if(ch == '\r') - { - s_ = s_chunk_size_lf; - break; - } - if(ch == ';') - { - s_ = s_chunk_ext_name0; - break; - } - auto v = unhex(ch); - if(v == -1) - return err(parse_error::invalid_chunk_size); - if(content_length_ > (no_content_length - 16) / 16) - return err(parse_error::bad_content_length); - content_length_ = - content_length_ * 16 + v; - break; - } - - case s_chunk_ext_name0: - if(! is_tchar(ch)) - return err(parse_error::invalid_ext_name); - s_ = s_chunk_ext_name; - break; - - case s_chunk_ext_name: - if(ch == '\r') - { - s_ = s_chunk_size_lf; - break; - } - if(ch == '=') - { - s_ = s_chunk_ext_val; - break; - } - if(ch == ';') - { - s_ = s_chunk_ext_name0; - break; - } - if(! is_tchar(ch)) - return err(parse_error::invalid_ext_name); - break; - - case s_chunk_ext_val: - if(ch == '\r') - { - s_ = s_chunk_size_lf; - break; - } - break; - - case s_chunk_size_lf: - if(ch != '\n') - return err(parse_error::bad_crlf); - if(content_length_ == 0) - { - flags_ |= parse_flag::trailing; - s_ = s_header_name0; - break; - } - //call_chunk_header(ec); if(ec) return errc(); - s_ = s_chunk_data0; - break; - - case s_chunk_data0: - BOOST_ASSERT(! cb_); - cb(&self::call_on_body); - s_ = s_chunk_data; - goto redo; // VFALCO fall through? - - case s_chunk_data: - { - std::size_t n; - if(static_cast((end - p)) < content_length_) - n = end - p; - else - n = static_cast(content_length_); - content_length_ -= n; - p += n - 1; - if(content_length_ == 0) - s_ = s_chunk_data_cr; - break; - } - - case s_chunk_data_cr: - if(ch != '\r') - return err(parse_error::bad_crlf); - if(cb(nullptr)) - return errc(); - s_ = s_chunk_data_lf; - break; - - case s_chunk_data_lf: - if(ch != '\n') - return err(parse_error::bad_crlf); - s_ = s_chunk_size0; - break; - - case s_complete: - ++p; - if(cb(nullptr)) - return errc(); - call_on_complete(ec); - if(ec) - return errc(); - s_ = s_restart; - return used(); - - case s_restart: - if(keep_alive()) - reset(); - else - s_ = s_dead; - goto redo; - } - } - if(cb_) - { - (this->*cb_)(ec, piece()); - if(ec) - return errc(); - } - return used(); -} - -template -void -basic_parser_v1:: -write_eof(error_code& ec) -{ - switch(s_) - { - case s_restart: - s_ = s_closed_complete; - break; - - case s_dead: - case s_closed_complete: - break; - - case s_body_identity_eof0: - case s_body_identity_eof: - cb_ = nullptr; - call_on_complete(ec); - if(ec) - { - s_ = s_dead; - break; - } - s_ = s_closed_complete; - break; - - default: - s_ = s_dead; - ec = parse_error::short_read; - break; - } -} - -template -void -basic_parser_v1:: -reset() -{ - cb_ = nullptr; - h_left_ = h_max_; - b_left_ = b_max_; - reset(std::integral_constant{}); -} - -template -bool -basic_parser_v1:: -needs_eof(std::true_type) const -{ - return false; -} - -template -bool -basic_parser_v1:: -needs_eof(std::false_type) const -{ - // See RFC 2616 section 4.4 - if( status_code_ / 100 == 1 || // 1xx e.g. Continue - status_code_ == 204 || // No Content - status_code_ == 304 || // Not Modified - flags_ & parse_flag::skipbody) // response to a HEAD request - return false; - - if((flags_ & parse_flag::chunked) || - content_length_ != no_content_length) - return false; - - return true; -} - -} // http -} // beast - -#endif diff --git a/include/beast/http/impl/error.ipp b/include/beast/http/impl/error.ipp new file mode 100644 index 0000000000..fc2438513d --- /dev/null +++ b/include/beast/http/impl/error.ipp @@ -0,0 +1,115 @@ +// +// 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_HTTP_IMPL_ERROR_IPP +#define BEAST_HTTP_IMPL_ERROR_IPP + +#include + +namespace boost { +namespace system { +template<> +struct is_error_code_enum +{ + static bool const value = true; +}; +} // system +} // boost + +namespace beast { +namespace http { +namespace detail { + +class http_error_category : public error_category +{ +public: + const char* + name() const noexcept override + { + return "beast.http"; + } + + std::string + message(int ev) const override + { + switch(static_cast(ev)) + { + case error::end_of_stream: return "end of stream"; + case error::partial_message: return "partial message"; + case error::need_more: return "need more"; + case error::unexpected_body: return "unexpected body"; + case error::need_buffer: return "need buffer"; + case error::buffer_overflow: return "buffer overflow"; + case error::header_limit: return "header limit exceeded"; + case error::body_limit: return "body limit exceeded"; + case error::bad_alloc: return "bad alloc"; + case error::bad_line_ending: return "bad line ending"; + case error::bad_method: return "bad method"; + case error::bad_target: return "bad target"; + case error::bad_version: return "bad version"; + case error::bad_status: return "bad status"; + case error::bad_reason: return "bad reason"; + case error::bad_field: return "bad field"; + case error::bad_value: return "bad value"; + case error::bad_content_length: return "bad Content-Length"; + case error::bad_transfer_encoding: return "bad Transfer-Encoding"; + case error::bad_chunk: return "bad chunk"; + case error::bad_obs_fold: return "bad obs-fold"; + + default: + return "beast.http error"; + } + } + + error_condition + default_error_condition( + int ev) const noexcept override + { + return error_condition{ev, *this}; + } + + bool + equivalent(int ev, + error_condition const& condition + ) const noexcept override + { + return condition.value() == ev && + &condition.category() == this; + } + + bool + equivalent(error_code const& error, + int ev) const noexcept override + { + return error.value() == ev && + &error.category() == this; + } +}; + +inline +error_category const& +get_http_error_category() +{ + static http_error_category const cat{}; + return cat; +} + +} // detail + +inline +error_code +make_error_code(error ev) +{ + return error_code{ + static_cast::type>(ev), + detail::get_http_error_category()}; +} + +} // http +} // beast + +#endif diff --git a/include/beast/http/impl/field.ipp b/include/beast/http/impl/field.ipp new file mode 100644 index 0000000000..6d4f2c5029 --- /dev/null +++ b/include/beast/http/impl/field.ipp @@ -0,0 +1,557 @@ +// +// 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_HTTP_IMPL_FIELD_IPP +#define BEAST_HTTP_IMPL_FIELD_IPP + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +namespace detail { + +struct field_table +{ + using array_type = + std::array; + + struct hash + { + std::size_t + operator()(string_view const& s) const + { + auto const n = s.size(); + return + beast::detail::ascii_tolower(s[0]) * + beast::detail::ascii_tolower(s[n/2]) ^ + beast::detail::ascii_tolower(s[n-1]); // hist[] = 331, 10, max_load_factor = 0.15f + } + }; + + struct iequal + { + // assumes inputs have equal length + bool + operator()( + string_view const& lhs, + string_view const& rhs) const + { + auto p1 = lhs.data(); + auto p2 = rhs.data(); + auto pend = lhs.end(); + char a, b; + while(p1 < pend) + { + a = *p1++; + b = *p2++; + if(a != b) + goto slow; + } + return true; + + while(p1 < pend) + { + slow: + if( beast::detail::ascii_tolower(a) != + beast::detail::ascii_tolower(b)) + return false; + a = *p1++; + b = *p2++; + } + return true; + } + }; + + using map_type = std::unordered_map< + string_view, field, hash, iequal>; + + array_type by_name_; + std::vector by_size_; +/* + From: + + https://www.iana.org/assignments/message-headers/message-headers.xhtml +*/ + field_table() + : by_name_({{ + "", + "A-IM", + "Accept", + "Accept-Additions", + "Accept-Charset", + "Accept-Datetime", + "Accept-Encoding", + "Accept-Features", + "Accept-Language", + "Accept-Patch", + "Accept-Post", + "Accept-Ranges", + "Access-Control", + "Access-Control-Allow-Credentials", + "Access-Control-Allow-Headers", + "Access-Control-Allow-Methods", + "Access-Control-Allow-Origin", + "Access-Control-Max-Age", + "Access-Control-Request-Headers", + "Access-Control-Request-Method", + "Age", + "Allow", + "ALPN", + "Also-Control", + "Alt-Svc", + "Alt-Used", + "Alternate-Recipient", + "Alternates", + "Apparently-To", + "Apply-To-Redirect-Ref", + "Approved", + "Archive", + "Archived-At", + "Article-Names", + "Article-Updates", + "Authentication-Control", + "Authentication-Info", + "Authentication-Results", + "Authorization", + "Auto-Submitted", + "Autoforwarded", + "Autosubmitted", + "Base", + "Bcc", + "Body", + "C-Ext", + "C-Man", + "C-Opt", + "C-PEP", + "C-PEP-Info", + "Cache-Control", + "CalDAV-Timezones", + "Cancel-Key", + "Cancel-Lock", + "Cc", + "Close", + "Comments", + "Compliance", + "Connection", + "Content-Alternative", + "Content-Base", + "Content-Description", + "Content-Disposition", + "Content-Duration", + "Content-Encoding", + "Content-features", + "Content-ID", + "Content-Identifier", + "Content-Language", + "Content-Length", + "Content-Location", + "Content-MD5", + "Content-Range", + "Content-Return", + "Content-Script-Type", + "Content-Style-Type", + "Content-Transfer-Encoding", + "Content-Type", + "Content-Version", + "Control", + "Conversion", + "Conversion-With-Loss", + "Cookie", + "Cookie2", + "Cost", + "DASL", + "Date", + "Date-Received", + "DAV", + "Default-Style", + "Deferred-Delivery", + "Delivery-Date", + "Delta-Base", + "Depth", + "Derived-From", + "Destination", + "Differential-ID", + "Digest", + "Discarded-X400-IPMS-Extensions", + "Discarded-X400-MTS-Extensions", + "Disclose-Recipients", + "Disposition-Notification-Options", + "Disposition-Notification-To", + "Distribution", + "DKIM-Signature", + "DL-Expansion-History", + "Downgraded-Bcc", + "Downgraded-Cc", + "Downgraded-Disposition-Notification-To", + "Downgraded-Final-Recipient", + "Downgraded-From", + "Downgraded-In-Reply-To", + "Downgraded-Mail-From", + "Downgraded-Message-Id", + "Downgraded-Original-Recipient", + "Downgraded-Rcpt-To", + "Downgraded-References", + "Downgraded-Reply-To", + "Downgraded-Resent-Bcc", + "Downgraded-Resent-Cc", + "Downgraded-Resent-From", + "Downgraded-Resent-Reply-To", + "Downgraded-Resent-Sender", + "Downgraded-Resent-To", + "Downgraded-Return-Path", + "Downgraded-Sender", + "Downgraded-To", + "EDIINT-Features", + "Eesst-Version", + "Encoding", + "Encrypted", + "Errors-To", + "ETag", + "Expect", + "Expires", + "Expiry-Date", + "Ext", + "Followup-To", + "Forwarded", + "From", + "Generate-Delivery-Report", + "GetProfile", + "Hobareg", + "Host", + "HTTP2-Settings", + "If", + "If-Match", + "If-Modified-Since", + "If-None-Match", + "If-Range", + "If-Schedule-Tag-Match", + "If-Unmodified-Since", + "IM", + "Importance", + "In-Reply-To", + "Incomplete-Copy", + "Injection-Date", + "Injection-Info", + "Jabber-ID", + "Keep-Alive", + "Keywords", + "Label", + "Language", + "Last-Modified", + "Latest-Delivery-Time", + "Lines", + "Link", + "List-Archive", + "List-Help", + "List-ID", + "List-Owner", + "List-Post", + "List-Subscribe", + "List-Unsubscribe", + "List-Unsubscribe-Post", + "Location", + "Lock-Token", + "Man", + "Max-Forwards", + "Memento-Datetime", + "Message-Context", + "Message-ID", + "Message-Type", + "Meter", + "Method-Check", + "Method-Check-Expires", + "MIME-Version", + "MMHS-Acp127-Message-Identifier", + "MMHS-Authorizing-Users", + "MMHS-Codress-Message-Indicator", + "MMHS-Copy-Precedence", + "MMHS-Exempted-Address", + "MMHS-Extended-Authorisation-Info", + "MMHS-Handling-Instructions", + "MMHS-Message-Instructions", + "MMHS-Message-Type", + "MMHS-Originator-PLAD", + "MMHS-Originator-Reference", + "MMHS-Other-Recipients-Indicator-CC", + "MMHS-Other-Recipients-Indicator-To", + "MMHS-Primary-Precedence", + "MMHS-Subject-Indicator-Codes", + "MT-Priority", + "Negotiate", + "Newsgroups", + "NNTP-Posting-Date", + "NNTP-Posting-Host", + "Non-Compliance", + "Obsoletes", + "Opt", + "Optional", + "Optional-WWW-Authenticate", + "Ordering-Type", + "Organization", + "Origin", + "Original-Encoded-Information-Types", + "Original-From", + "Original-Message-ID", + "Original-Recipient", + "Original-Sender", + "Original-Subject", + "Originator-Return-Address", + "Overwrite", + "P3P", + "Path", + "PEP", + "Pep-Info", + "PICS-Label", + "Position", + "Posting-Version", + "Pragma", + "Prefer", + "Preference-Applied", + "Prevent-NonDelivery-Report", + "Priority", + "Privicon", + "ProfileObject", + "Protocol", + "Protocol-Info", + "Protocol-Query", + "Protocol-Request", + "Proxy-Authenticate", + "Proxy-Authentication-Info", + "Proxy-Authorization", + "Proxy-Connection", + "Proxy-Features", + "Proxy-Instruction", + "Public", + "Public-Key-Pins", + "Public-Key-Pins-Report-Only", + "Range", + "Received", + "Received-SPF", + "Redirect-Ref", + "References", + "Referer", + "Referer-Root", + "Relay-Version", + "Reply-By", + "Reply-To", + "Require-Recipient-Valid-Since", + "Resent-Bcc", + "Resent-Cc", + "Resent-Date", + "Resent-From", + "Resent-Message-ID", + "Resent-Reply-To", + "Resent-Sender", + "Resent-To", + "Resolution-Hint", + "Resolver-Location", + "Retry-After", + "Return-Path", + "Safe", + "Schedule-Reply", + "Schedule-Tag", + "Sec-WebSocket-Accept", + "Sec-WebSocket-Extensions", + "Sec-WebSocket-Key", + "Sec-WebSocket-Protocol", + "Sec-WebSocket-Version", + "Security-Scheme", + "See-Also", + "Sender", + "Sensitivity", + "Server", + "Set-Cookie", + "Set-Cookie2", + "SetProfile", + "SIO-Label", + "SIO-Label-History", + "SLUG", + "SoapAction", + "Solicitation", + "Status-URI", + "Strict-Transport-Security", + "Subject", + "SubOK", + "Subst", + "Summary", + "Supersedes", + "Surrogate-Capability", + "Surrogate-Control", + "TCN", + "TE", + "Timeout", + "Title", + "To", + "Topic", + "Trailer", + "Transfer-Encoding", + "TTL", + "UA-Color", + "UA-Media", + "UA-Pixels", + "UA-Resolution", + "UA-Windowpixels", + "Upgrade", + "Urgency", + "URI", + "User-Agent", + "Variant-Vary", + "Vary", + "VBR-Info", + "Version", + "Via", + "Want-Digest", + "Warning", + "WWW-Authenticate", + "X-Archived-At", + "X-Device-Accept", + "X-Device-Accept-Charset", + "X-Device-Accept-Encoding", + "X-Device-Accept-Language", + "X-Device-User-Agent", + "X-Frame-Options", + "X-Mittente", + "X-PGP-Sig", + "X-Ricevuta", + "X-Riferimento-Message-ID", + "X-TipoRicevuta", + "X-Trasporto", + "X-VerificaSicurezza", + "X400-Content-Identifier", + "X400-Content-Return", + "X400-Content-Type", + "X400-MTS-Identifier", + "X400-Originator", + "X400-Received", + "X400-Recipients", + "X400-Trace", + "Xref" + }}) + { + // find the longest field length + std::size_t high = 0; + for(auto const& s : by_name_) + if(high < s.size()) + high = s.size(); + // build by_size map + // skip field::unknown + by_size_.resize(high + 1); + for(auto& map : by_size_) + map.max_load_factor(.15f); + for(std::size_t i = 1; + i < by_name_.size(); ++i) + { + auto const& s = by_name_[i]; + by_size_[s.size()].emplace( + s, static_cast(i)); + } + +#if 0 + // This snippet calculates the performance + // of the hash function and map settings + { + std::vector hist; + for(auto const& map : by_size_) + { + for(std::size_t i = 0; i < map.bucket_count(); ++i) + { + auto const n = map.bucket_size(i); + if(n > 0) + { + if(hist.size() < n) + hist.resize(n); + ++hist[n-1]; + } + } + } + } +#endif + } + + field + string_to_field(string_view s) const + { + if(s.size() >= by_size_.size()) + return field::unknown; + auto const& map = by_size_[s.size()]; + if(map.empty()) + return field::unknown; + auto it = map.find(s); + if(it == map.end()) + return field::unknown; + return it->second; + } + + // + // Deprecated + // + + using const_iterator = + array_type::const_iterator; + + std::size_t + size() const + { + return by_name_.size(); + } + + const_iterator + begin() const + { + return by_name_.begin(); + } + + const_iterator + end() const + { + return by_name_.end(); + } +}; + +inline +field_table const& +get_field_table() +{ + static field_table const tab; + return tab; +} + +template +string_view +to_string(field f) +{ + auto const& v = get_field_table(); + BOOST_ASSERT(static_cast(f) < v.size()); + return v.begin()[static_cast(f)]; +} + +} // detail + +inline +string_view +to_string(field f) +{ + return detail::to_string(f); +} + +inline +field +string_to_field(string_view s) +{ + return detail::get_field_table().string_to_field(s); +} + +} // http +} // beast + +#endif diff --git a/include/beast/http/impl/fields.ipp b/include/beast/http/impl/fields.ipp new file mode 100644 index 0000000000..f5b4a1598d --- /dev/null +++ b/include/beast/http/impl/fields.ipp @@ -0,0 +1,1348 @@ +// +// 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_HTTP_IMPL_FIELDS_IPP +#define BEAST_HTTP_IMPL_FIELDS_IPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(BOOST_LIBSTDCXX_VERSION) && BOOST_LIBSTDCXX_VERSION < 60000 + // Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56437 +#ifndef BEAST_HTTP_NO_FIELDS_BASIC_STRING_ALLOCATOR +#define BEAST_HTTP_NO_FIELDS_BASIC_STRING_ALLOCATOR +#endif +#endif + +namespace beast { +namespace http { + +template +class basic_fields::reader +{ +public: + using iter_type = typename list_t::const_iterator; + + struct field_iterator + { + iter_type it_; + + using value_type = boost::asio::const_buffer; + using pointer = value_type const*; + using reference = value_type const; + using difference_type = std::ptrdiff_t; + using iterator_category = + std::bidirectional_iterator_tag; + + field_iterator() = default; + field_iterator(field_iterator&& other) = default; + field_iterator(field_iterator const& other) = default; + field_iterator& operator=(field_iterator&& other) = default; + field_iterator& operator=(field_iterator const& other) = default; + + explicit + field_iterator(iter_type it) + : it_(it) + { + } + + bool + operator==(field_iterator const& other) const + { + return it_ == other.it_; + } + + bool + operator!=(field_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const + { + return it_->buffer(); + } + + field_iterator& + operator++() + { + ++it_; + return *this; + } + + field_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + + field_iterator& + operator--() + { + --it_; + return *this; + } + + field_iterator + operator--(int) + { + auto temp = *this; + --(*this); + return temp; + } + }; + + class field_range + { + field_iterator first_; + field_iterator last_; + + public: + using const_iterator = + field_iterator; + + using value_type = + typename const_iterator::value_type; + + field_range(field_range const&) = default; + + field_range(iter_type first, iter_type last) + : first_(first) + , last_(last) + { + } + + const_iterator + begin() const + { + return first_; + } + + const_iterator + end() const + { + return last_; + } + }; + + basic_fields const& f_; + boost::asio::const_buffer cb_[3]; + char buf_[13]; + +public: + using const_buffers_type = + buffer_cat_view< + boost::asio::const_buffers_1, + boost::asio::const_buffers_1, + boost::asio::const_buffers_1, + field_range, + boost::asio::const_buffers_1>; + + reader(basic_fields const& f, + unsigned version, verb v); + + reader(basic_fields const& f, + unsigned version, unsigned code); + + const_buffers_type + get() const + { + return buffer_cat( + boost::asio::const_buffers_1{cb_[0]}, + boost::asio::const_buffers_1{cb_[1]}, + boost::asio::const_buffers_1{cb_[2]}, + field_range(f_.list_.begin(), f_.list_.end()), + detail::chunk_crlf()); + } +}; + +template +basic_fields::reader:: +reader(basic_fields const& f, + unsigned version, verb v) + : f_(f) +{ +/* + request + "" + " " + " HTTP/X.Y\r\n" (11 chars) +*/ + string_view sv; + if(v == verb::unknown) + sv = f_.get_method_impl(); + else + sv = to_string(v); + cb_[0] = {sv.data(), sv.size()}; + + // target_or_reason_ has a leading SP + cb_[1] = { + f_.target_or_reason_.data(), + f_.target_or_reason_.size()}; + + buf_[0] = ' '; + buf_[1] = 'H'; + buf_[2] = 'T'; + buf_[3] = 'T'; + buf_[4] = 'P'; + buf_[5] = '/'; + buf_[6] = '0' + static_cast(version / 10); + buf_[7] = '.'; + buf_[8] = '0' + static_cast(version % 10); + buf_[9] = '\r'; + buf_[10]= '\n'; + cb_[2] = {buf_, 11}; +} + +template +basic_fields::reader:: +reader(basic_fields const& f, + unsigned version, unsigned code) + : f_(f) +{ +/* + response + "HTTP/X.Y ### " (13 chars) + "" + "\r\n" +*/ + buf_[0] = 'H'; + buf_[1] = 'T'; + buf_[2] = 'T'; + buf_[3] = 'P'; + buf_[4] = '/'; + buf_[5] = '0' + static_cast(version / 10); + buf_[6] = '.'; + buf_[7] = '0' + static_cast(version % 10); + buf_[8] = ' '; + buf_[9] = '0' + static_cast(code / 100); + buf_[10]= '0' + static_cast((code / 10) % 10); + buf_[11]= '0' + static_cast(code % 10); + buf_[12]= ' '; + cb_[0] = {buf_, 13}; + + string_view sv; + if(! f_.target_or_reason_.empty()) + sv = f_.target_or_reason_; + else + sv = obsolete_reason(static_cast(code)); + cb_[1] = {sv.data(), sv.size()}; + + cb_[2] = detail::chunk_crlf(); +} + +//------------------------------------------------------------------------------ + +template +basic_fields:: +value_type:: +value_type(field name, + string_view sname, string_view value) + : off_(static_cast(sname.size() + 2)) + , len_(static_cast(value.size())) + , f_(name) +{ + //BOOST_ASSERT(name == field::unknown || + // iequals(sname, to_string(name))); + char* p = reinterpret_cast(this + 1); + p[off_-2] = ':'; + p[off_-1] = ' '; + p[off_ + len_] = '\r'; + p[off_ + len_ + 1] = '\n'; + std::memcpy(p, sname.data(), sname.size()); + std::memcpy(p + off_, value.data(), value.size()); +} + +template +inline +field +basic_fields:: +value_type:: +name() const +{ + return f_; +} + +template +inline +string_view +basic_fields:: +value_type:: +name_string() const +{ + return {reinterpret_cast< + char const*>(this + 1), + static_cast(off_ - 2)}; +} + +template +inline +string_view +basic_fields:: +value_type:: +value() const +{ + return {reinterpret_cast< + char const*>(this + 1) + off_, + static_cast(len_)}; +} + +template +inline +boost::asio::const_buffer +basic_fields:: +value_type:: +buffer() const +{ + return boost::asio::const_buffer{ + reinterpret_cast(this + 1), + static_cast(off_) + len_ + 2}; +} + +//------------------------------------------------------------------------------ + +template +basic_fields:: +~basic_fields() +{ + delete_list(); + realloc_string(method_, {}); + realloc_string( + target_or_reason_, {}); +} + +template +basic_fields:: +basic_fields(Allocator const& alloc) + : alloc_(alloc) +{ +} + +template +basic_fields:: +basic_fields(basic_fields&& other) + : alloc_(std::move(other.alloc_)) + , set_(std::move(other.set_)) + , list_(std::move(other.list_)) + , method_(other.method_) + , target_or_reason_(other.target_or_reason_) +{ + other.method_.clear(); + other.target_or_reason_.clear(); +} + +template +basic_fields:: +basic_fields(basic_fields&& other, Allocator const& alloc) + : alloc_(alloc) +{ + if(alloc_ != other.alloc_) + { + copy_all(other); + other.clear_all(); + } + else + { + set_ = std::move(other.set_); + list_ = std::move(other.list_); + method_ = other.method_; + target_or_reason_ = other.target_or_reason_; + } +} + +template +basic_fields:: +basic_fields(basic_fields const& other) + : alloc_(alloc_traits:: + select_on_container_copy_construction(other.alloc_)) +{ + copy_all(other); +} + +template +basic_fields:: +basic_fields(basic_fields const& other, + Allocator const& alloc) + : alloc_(alloc) +{ + copy_all(other); +} + +template +template +basic_fields:: +basic_fields(basic_fields const& other) +{ + copy_all(other); +} + +template +template +basic_fields:: +basic_fields(basic_fields const& other, + Allocator const& alloc) + : alloc_(alloc) +{ + copy_all(other); +} + +template +auto +basic_fields:: +operator=(basic_fields&& other) -> + basic_fields& +{ + if(this == &other) + return *this; + move_assign(other, typename alloc_traits:: + propagate_on_container_move_assignment{}); + return *this; +} + +template +auto +basic_fields:: +operator=(basic_fields const& other) -> + basic_fields& +{ + copy_assign(other, typename alloc_traits:: + propagate_on_container_copy_assignment{}); + return *this; +} + +template +template +auto +basic_fields:: +operator=(basic_fields const& other) -> + basic_fields& +{ + clear_all(); + copy_all(other); + return *this; +} + +//------------------------------------------------------------------------------ +// +// Element access +// +//------------------------------------------------------------------------------ + +template +string_view +basic_fields:: +at(field name) const +{ + BOOST_ASSERT(name != field::unknown); + auto const it = find(name); + if(it == end()) + BOOST_THROW_EXCEPTION(std::out_of_range{ + "field not found"}); + return it->value(); +} + +template +string_view +basic_fields:: +at(string_view name) const +{ + auto const it = find(name); + if(it == end()) + BOOST_THROW_EXCEPTION(std::out_of_range{ + "field not found"}); + return it->value(); +} + +template +string_view +basic_fields:: +operator[](field name) const +{ + BOOST_ASSERT(name != field::unknown); + auto const it = find(name); + if(it == end()) + return {}; + return it->value(); +} + +template +string_view +basic_fields:: +operator[](string_view name) const +{ + auto const it = find(name); + if(it == end()) + return {}; + return it->value(); +} + +//------------------------------------------------------------------------------ +// +// Modifiers +// +//------------------------------------------------------------------------------ + +template +void +basic_fields:: +clear() +{ + delete_list(); + set_.clear(); + list_.clear(); +} + +template +inline +void +basic_fields:: +insert(field name, string_param const& value) +{ + BOOST_ASSERT(name != field::unknown); + insert(name, to_string(name), value); +} + +template +void +basic_fields:: +insert(string_view sname, string_param const& value) +{ + auto const name = + string_to_field(sname); + insert(name, sname, value); +} + +template +void +basic_fields:: +insert(field name, + string_view sname, string_param const& value) +{ + auto& e = new_element(name, sname, + static_cast(value)); + auto const before = + set_.upper_bound(sname, key_compare{}); + if(before == set_.begin()) + { + BOOST_ASSERT(count(sname) == 0); + set_.insert_before(before, e); + list_.push_back(e); + return; + } + auto const last = std::prev(before); + // VFALCO is it worth comparing `field name` first? + if(! iequals(sname, last->name_string())) + { + BOOST_ASSERT(count(sname) == 0); + set_.insert_before(before, e); + list_.push_back(e); + return; + } + // keep duplicate fields together in the list + set_.insert_before(before, e); + list_.insert(++list_.iterator_to(*last), e); +} + +template +void +basic_fields:: +set(field name, string_param const& value) +{ + BOOST_ASSERT(name != field::unknown); + set_element(new_element(name, to_string(name), + static_cast(value))); +} + +template +void +basic_fields:: +set(string_view sname, string_param const& value) +{ + set_element(new_element( + string_to_field(sname), sname, + static_cast(value))); +} + +template +auto +basic_fields:: +erase(const_iterator pos) -> + const_iterator +{ + auto next = pos.iter(); + auto& e = *next++; + set_.erase(e); + list_.erase(e); + delete_element(e); + return next; +} + +template +std::size_t +basic_fields:: +erase(field name) +{ + BOOST_ASSERT(name != field::unknown); + return erase(to_string(name)); +} + +template +std::size_t +basic_fields:: +erase(string_view name) +{ + std::size_t n =0; + set_.erase_and_dispose(name, key_compare{}, + [&](value_type* e) + { + ++n; + list_.erase(list_.iterator_to(*e)); + delete_element(*e); + }); + return n; +} + +template +void +basic_fields:: +swap(basic_fields& other) +{ + swap(other, typename alloc_traits:: + propagate_on_container_swap{}); +} + +template +void +swap( + basic_fields& lhs, + basic_fields& rhs) +{ + lhs.swap(rhs); +} + +//------------------------------------------------------------------------------ +// +// Lookup +// +//------------------------------------------------------------------------------ + +template +inline +std::size_t +basic_fields:: +count(field name) const +{ + BOOST_ASSERT(name != field::unknown); + return count(to_string(name)); +} + +template +std::size_t +basic_fields:: +count(string_view name) const +{ + return set_.count(name, key_compare{}); +} + +template +inline +auto +basic_fields:: +find(field name) const -> + const_iterator +{ + BOOST_ASSERT(name != field::unknown); + return find(to_string(name)); +} + +template +auto +basic_fields:: +find(string_view name) const -> + const_iterator +{ + auto const it = set_.find( + name, key_compare{}); + if(it == set_.end()) + return list_.end(); + return list_.iterator_to(*it); +} + +template +inline +auto +basic_fields:: +equal_range(field name) const -> + std::pair +{ + BOOST_ASSERT(name != field::unknown); + return equal_range(to_string(name)); +} + +template +auto +basic_fields:: +equal_range(string_view name) const -> + std::pair +{ + auto const result = + set_.equal_range(name, key_compare{}); + return { + list_.iterator_to(result->first), + list_.iterator_to(result->second)}; +} + +//------------------------------------------------------------------------------ + +namespace detail { + +// Filter a token list +// +template +void +filter_token_list( + String& s, + string_view const& value, + Pred&& pred) +{ + token_list te{value}; + auto it = te.begin(); + auto last = te.end(); + if(it == last) + return; + while(pred(*it)) + if(++it == last) + return; + s.append(it->data(), it->size()); + while(++it != last) + { + if(! pred(*it)) + { + s.append(", "); + s.append(it->data(), it->size()); + } + } +} + +// Filter the last item in a token list +template +void +filter_token_list_last( + String& s, + string_view const& value, + Pred&& pred) +{ + token_list te{value}; + if(te.begin() != te.end()) + { + auto it = te.begin(); + auto next = std::next(it); + if(next == te.end()) + { + if(! pred(*it)) + s.append(it->data(), it->size()); + return; + } + s.append(it->data(), it->size()); + for(;;) + { + it = next; + next = std::next(it); + if(next == te.end()) + { + if(! pred(*it)) + { + s.append(", "); + s.append(it->data(), it->size()); + } + return; + } + s.append(", "); + s.append(it->data(), it->size()); + } + } +} + +template +void +keep_alive_impl( + String& s, string_view const& value, + unsigned version, bool keep_alive) +{ + if(version < 11) + { + if(keep_alive) + { + // remove close + filter_token_list(s, value, + [](string_view s) + { + return iequals(s, "close"); + }); + // add keep-alive + if(s.empty()) + s.append("keep-alive"); + else if(! token_list{value}.exists("keep-alive")) + s.append(", keep-alive"); + } + else + { + // remove close and keep-alive + filter_token_list(s, value, + [](string_view s) + { + return + iequals(s, "close") || + iequals(s, "keep-alive"); + }); + } + } + else + { + if(keep_alive) + { + // remove close and keep-alive + filter_token_list(s, value, + [](string_view s) + { + return + iequals(s, "close") || + iequals(s, "keep-alive"); + }); + } + else + { + // remove keep-alive + filter_token_list(s, value, + [](string_view s) + { + return iequals(s, "keep-alive"); + }); + // add close + if(s.empty()) + s.append("close"); + else if(! token_list{value}.exists("close")) + s.append(", close"); + } + } +} + +} // detail + +//------------------------------------------------------------------------------ + +// Fields + +template +inline +string_view +basic_fields:: +get_method_impl() const +{ + return method_; +} + +template +inline +string_view +basic_fields:: +get_target_impl() const +{ + if(target_or_reason_.empty()) + return target_or_reason_; + return { + target_or_reason_.data() + 1, + target_or_reason_.size() - 1}; +} + +template +inline +string_view +basic_fields:: +get_reason_impl() const +{ + return target_or_reason_; +} + +template +bool +basic_fields:: +get_chunked_impl() const +{ + auto const te = token_list{ + (*this)[field::transfer_encoding]}; + for(auto it = te.begin(); it != te.end();) + { + auto const next = std::next(it); + if(next == te.end()) + return iequals(*it, "chunked"); + it = next; + } + return false; +} + +template +bool +basic_fields:: +get_keep_alive_impl(unsigned version) const +{ + auto const it = find(field::connection); + if(version < 11) + { + if(it == end()) + return false; + return token_list{ + it->value()}.exists("keep-alive"); + } + if(it == end()) + return true; + return ! token_list{ + it->value()}.exists("close"); +} + +template +inline +void +basic_fields:: +set_method_impl(string_view s) +{ + realloc_string(method_, s); +} + +template +inline +void +basic_fields:: +set_target_impl(string_view s) +{ + realloc_target( + target_or_reason_, s); +} + +template +inline +void +basic_fields:: +set_reason_impl(string_view s) +{ + realloc_string( + target_or_reason_, s); +} + +template +void +basic_fields:: +set_chunked_impl(bool value) +{ + auto it = find(field::transfer_encoding); + if(value) + { + // append "chunked" + if(it == end()) + { + set(field::transfer_encoding, "chunked"); + return; + } + auto const te = token_list{it->value()}; + for(auto itt = te.begin();;) + { + auto const next = std::next(itt); + if(next == te.end()) + { + if(iequals(*itt, "chunked")) + return; // already set + break; + } + itt = next; + } + static_string buf; + if(it->value().size() <= buf.size() + 9) + { + buf.append(it->value().data(), it->value().size()); + buf.append(", chunked", 9); + set(field::transfer_encoding, buf); + } + else + { + #ifdef BEAST_HTTP_NO_FIELDS_BASIC_STRING_ALLOCATOR + // Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56437 + std::string s; + #else + using rebind_type = + typename std::allocator_traits< + Allocator>::template rebind_alloc; + std::basic_string< + char, + std::char_traits, + rebind_type> s{rebind_type{alloc_}}; + #endif + s.reserve(it->value().size() + 9); + s.append(it->value().data(), it->value().size()); + s.append(", chunked", 9); + set(field::transfer_encoding, s); + } + return; + } + // filter "chunked" + if(it == end()) + return; + try + { + static_string buf; + detail::filter_token_list_last(buf, it->value(), + [](string_view const& s) + { + return iequals(s, "chunked"); + }); + if(! buf.empty()) + set(field::transfer_encoding, buf); + else + erase(field::transfer_encoding); + } + catch(std::length_error const&) + { + #ifdef BEAST_HTTP_NO_FIELDS_BASIC_STRING_ALLOCATOR + // Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56437 + std::string s; + #else + using rebind_type = + typename std::allocator_traits< + Allocator>::template rebind_alloc; + std::basic_string< + char, + std::char_traits, + rebind_type> s{rebind_type{alloc_}}; + #endif + s.reserve(it->value().size()); + detail::filter_token_list_last(s, it->value(), + [](string_view const& s) + { + return iequals(s, "chunked"); + }); + if(! s.empty()) + set(field::transfer_encoding, s); + else + erase(field::transfer_encoding); + } +} + +template +void +basic_fields:: +set_content_length_impl( + boost::optional const& value) +{ + if(! value) + erase(field::content_length); + else + set(field::content_length, *value); +} + +template +void +basic_fields:: +set_keep_alive_impl( + unsigned version, bool keep_alive) +{ + // VFALCO What about Proxy-Connection ? + auto const value = (*this)[field::connection]; + try + { + static_string buf; + detail::keep_alive_impl( + buf, value, version, keep_alive); + if(buf.empty()) + erase(field::connection); + else + set(field::connection, buf); + } + catch(std::length_error const&) + { + #ifdef BEAST_HTTP_NO_FIELDS_BASIC_STRING_ALLOCATOR + // Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56437 + std::string s; + #else + using rebind_type = + typename std::allocator_traits< + Allocator>::template rebind_alloc; + std::basic_string< + char, + std::char_traits, + rebind_type> s{rebind_type{alloc_}}; + #endif + s.reserve(value.size()); + detail::keep_alive_impl( + s, value, version, keep_alive); + if(s.empty()) + erase(field::connection); + else + set(field::connection, s); + } +} + +//------------------------------------------------------------------------------ + +template +auto +basic_fields:: +new_element(field name, + string_view sname, string_view value) -> + value_type& +{ + if(sname.size() + 2 > + (std::numeric_limits::max)()) + BOOST_THROW_EXCEPTION(std::length_error{ + "field name too large"}); + if(value.size() + 2 > + (std::numeric_limits::max)()) + BOOST_THROW_EXCEPTION(std::length_error{ + "field value too large"}); + value = detail::trim(value); + std::uint16_t const off = + static_cast(sname.size() + 2); + std::uint16_t const len = + static_cast(value.size()); + auto const p = alloc_traits::allocate(alloc_, + 1 + (off + len + 2 + sizeof(value_type) - 1) / + sizeof(value_type)); + // VFALCO allocator can't call the constructor because its private + //alloc_traits::construct(alloc_, p, name, sname, value); + new(p) value_type{name, sname, value}; + return *p; +} + +template +void +basic_fields:: +delete_element(value_type& e) +{ + auto const n = 1 + (e.off_ + e.len_ + 2 + + sizeof(value_type) - 1) / sizeof(value_type); + alloc_traits::destroy(alloc_, &e); + alloc_traits::deallocate(alloc_, &e, n); +} + +template +void +basic_fields:: +set_element(value_type& e) +{ + auto it = set_.lower_bound( + e.name_string(), key_compare{}); + if(it == set_.end() || ! iequals( + e.name_string(), it->name_string())) + { + set_.insert_before(it, e); + list_.push_back(e); + return; + } + for(;;) + { + auto next = it; + ++next; + set_.erase(it); + list_.erase(list_.iterator_to(*it)); + delete_element(*it); + it = next; + if(it == set_.end() || + ! iequals(e.name_string(), it->name_string())) + break; + } + set_.insert_before(it, e); + list_.push_back(e); +} + +template +void +basic_fields:: +realloc_string(string_view& dest, string_view s) +{ + if(dest.empty() && s.empty()) + return; + auto a = typename std::allocator_traits< + Allocator>::template rebind_alloc< + char>(alloc_); + if(! dest.empty()) + { + a.deallocate(const_cast( + dest.data()), dest.size()); + dest.clear(); + } + if(! s.empty()) + { + auto const p = a.allocate(s.size()); + std::memcpy(p, s.data(), s.size()); + dest = {p, s.size()}; + } +} + +template +void +basic_fields:: +realloc_target( + string_view& dest, string_view s) +{ + // The target string are stored with an + // extra space at the beginning to help + // the reader class. + if(dest.empty() && s.empty()) + return; + auto a = typename std::allocator_traits< + Allocator>::template rebind_alloc< + char>(alloc_); + if(! dest.empty()) + { + a.deallocate(const_cast( + dest.data()), dest.size()); + dest.clear(); + } + if(! s.empty()) + { + auto const p = a.allocate(1 + s.size()); + p[0] = ' '; + std::memcpy(p + 1, s.data(), s.size()); + dest = {p, 1 + s.size()}; + } +} + +template +template +void +basic_fields:: +copy_all(basic_fields const& other) +{ + for(auto const& e : other.list_) + insert(e.name(), e.name_string(), e.value()); + realloc_string(method_, other.method_); + realloc_string(target_or_reason_, + other.target_or_reason_); +} + +template +void +basic_fields:: +clear_all() +{ + clear(); + realloc_string(method_, {}); + realloc_string(target_or_reason_, {}); +} + +template +void +basic_fields:: +delete_list() +{ + for(auto it = list_.begin(); it != list_.end();) + delete_element(*it++); +} + +//------------------------------------------------------------------------------ + +template +inline +void +basic_fields:: +move_assign(basic_fields& other, std::true_type) +{ + clear_all(); + set_ = std::move(other.set_); + list_ = std::move(other.list_); + method_ = other.method_; + target_or_reason_ = other.target_or_reason_; + other.method_.clear(); + other.target_or_reason_.clear(); + alloc_ = other.alloc_; +} + +template +inline +void +basic_fields:: +move_assign(basic_fields& other, std::false_type) +{ + clear_all(); + if(alloc_ != other.alloc_) + { + copy_all(other); + other.clear_all(); + } + else + { + set_ = std::move(other.set_); + list_ = std::move(other.list_); + method_ = other.method_; + target_or_reason_ = other.target_or_reason_; + other.method_.clear(); + other.target_or_reason_.clear(); + } +} + +template +inline +void +basic_fields:: +copy_assign(basic_fields const& other, std::true_type) +{ + clear_all(); + alloc_ = other.alloc_; + copy_all(other); +} + +template +inline +void +basic_fields:: +copy_assign(basic_fields const& other, std::false_type) +{ + clear_all(); + copy_all(other); +} + +template +inline +void +basic_fields:: +swap(basic_fields& other, std::true_type) +{ + using std::swap; + swap(alloc_, other.alloc_); + swap(set_, other.set_); + swap(list_, other.list_); + swap(method_, other.method_); + swap(target_or_reason_, other.target_or_reason_); +} + +template +inline +void +basic_fields:: +swap(basic_fields& other, std::false_type) +{ + BOOST_ASSERT(alloc_ == other.alloc_); + using std::swap; + swap(set_, other.set_); + swap(list_, other.list_); + swap(method_, other.method_); + swap(target_or_reason_, other.target_or_reason_); +} + +} // http +} // beast + +#endif diff --git a/include/beast/http/impl/file_body_win32.ipp b/include/beast/http/impl/file_body_win32.ipp new file mode 100644 index 0000000000..03734339fe --- /dev/null +++ b/include/beast/http/impl/file_body_win32.ipp @@ -0,0 +1,579 @@ +// +// 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_HTTP_IMPL_FILE_BODY_WIN32_IPP +#define BEAST_HTTP_IMPL_FILE_BODY_WIN32_IPP + +#if BEAST_USE_WIN32_FILE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +namespace detail { +template +class write_some_win32_op; +} // detail + +template<> +struct basic_file_body +{ + using file_type = file_win32; + + class reader; + class writer; + + //-------------------------------------------------------------------------- + + class value_type + { + friend class reader; + friend class writer; + friend struct basic_file_body; + + template + friend class detail::write_some_win32_op; + template + friend + void + write_some( + boost::asio::basic_stream_socket& sock, + serializer, + Fields, Decorator>& sr, + error_code& ec); + + file_win32 file_; + std::uint64_t size_ = 0; // cached file size + std::uint64_t first_; // starting offset of the range + std::uint64_t last_; // ending offset of the range + + public: + ~value_type() = default; + value_type() = default; + value_type(value_type&& other) = default; + value_type& operator=(value_type&& other) = default; + + bool + is_open() const + { + return file_.is_open(); + } + + std::uint64_t + size() const + { + return size_; + } + + void + close(); + + void + open(char const* path, file_mode mode, error_code& ec); + + void + reset(file_win32&& file, error_code& ec); + }; + + //-------------------------------------------------------------------------- + + class reader + { + template + friend class detail::write_some_win32_op; + template + friend + void + write_some( + boost::asio::basic_stream_socket& sock, + serializer, + Fields, Decorator>& sr, + error_code& ec); + + value_type& body_; // The body we are reading from + std::uint64_t pos_; // The current position in the file + char buf_[4096]; // Small buffer for reading + + public: + using const_buffers_type = + boost::asio::const_buffers_1; + + template + reader(message, Fields>& m) + : body_(m.body) + { + } + + void + init(error_code&) + { + BOOST_ASSERT(body_.file_.is_open()); + pos_ = body_.first_; + } + + boost::optional> + get(error_code& ec) + { + std::size_t const n = (std::min)(sizeof(buf_), + beast::detail::clamp(body_.last_ - pos_)); + if(n == 0) + { + ec.assign(0, ec.category()); + return boost::none; + } + auto const nread = body_.file_.read(buf_, n, ec); + if(ec) + return boost::none; + BOOST_ASSERT(nread != 0); + pos_ += nread; + ec.assign(0, ec.category()); + return {{ + {buf_, nread}, // buffer to return. + pos_ < body_.last_}}; // `true` if there are more buffers. + } + }; + + //-------------------------------------------------------------------------- + + class writer + { + value_type& body_; + + public: + template + explicit + writer(message& m) + : body_(m.body) + { + } + + void + init(boost::optional< + std::uint64_t> const& content_length, + error_code& ec) + { + // VFALCO We could reserve space in the file + boost::ignore_unused(content_length); + BOOST_ASSERT(body_.file_.is_open()); + ec.assign(0, ec.category()); + } + + template + std::size_t + put(ConstBufferSequence const& buffers, + error_code& ec) + { + std::size_t nwritten = 0; + for(boost::asio::const_buffer buffer : buffers) + { + nwritten += body_.file_.write( + boost::asio::buffer_cast(buffer), + boost::asio::buffer_size(buffer), + ec); + if(ec) + return nwritten; + } + ec.assign(0, ec.category()); + return nwritten; + } + + void + finish(error_code& ec) + { + ec.assign(0, ec.category()); + } + }; + + //-------------------------------------------------------------------------- + + static + std::uint64_t + size(value_type const& body) + { + return body.size(); + } +}; + +//------------------------------------------------------------------------------ + +inline +void +basic_file_body:: +value_type:: +close() +{ + error_code ignored; + file_.close(ignored); +} + +inline +void +basic_file_body:: +value_type:: +open(char const* path, file_mode mode, error_code& ec) +{ + file_.open(path, mode, ec); + if(ec) + return; + size_ = file_.size(ec); + if(ec) + { + close(); + return; + } + first_ = 0; + last_ = size_; +} + +inline +void +basic_file_body:: +value_type:: +reset(file_win32&& file, error_code& ec) +{ + if(file_.is_open()) + { + error_code ignored; + file_.close(ignored); + } + file_ = std::move(file); + if(file_.is_open()) + { + size_ = file_.size(ec); + if(ec) + { + close(); + return; + } + first_ = 0; + last_ = size_; + } +} + +//------------------------------------------------------------------------------ + +namespace detail { + +template +inline +boost::detail::winapi::DWORD_ +lowPart(Unsigned n) +{ + return static_cast< + boost::detail::winapi::DWORD_>( + n & 0xffffffff); +} + +template +inline +boost::detail::winapi::DWORD_ +highPart(Unsigned n, std::true_type) +{ + return static_cast< + boost::detail::winapi::DWORD_>( + (n>>32)&0xffffffff); +} + +template +inline +boost::detail::winapi::DWORD_ +highPart(Unsigned, std::false_type) +{ + return 0; +} + +template +inline +boost::detail::winapi::DWORD_ +highPart(Unsigned n) +{ + return highPart(n, std::integral_constant< + bool, (sizeof(Unsigned)>4)>{}); +} + +class null_lambda +{ +public: + template + void + operator()(error_code&, + ConstBufferSequence const&) const + { + BOOST_ASSERT(false); + } +}; + +//------------------------------------------------------------------------------ + +#if BOOST_ASIO_HAS_WINDOWS_OVERLAPPED_PTR + +template +class write_some_win32_op +{ + boost::asio::basic_stream_socket& sock_; + serializer, + Fields, Decorator>& sr_; + bool header_ = false; + Handler h_; + +public: + write_some_win32_op(write_some_win32_op&&) = default; + write_some_win32_op(write_some_win32_op const&) = default; + + template + write_some_win32_op( + DeducedHandler&& h, + boost::asio::basic_stream_socket& s, + serializer, + Fields, Decorator>& sr) + : sock_(s) + , sr_(sr) + , h_(std::forward(h)) + { + } + + void + operator()(); + + void + operator()(error_code ec, + std::size_t bytes_transferred = 0); + + friend + void* asio_handler_allocate( + std::size_t size, write_some_win32_op* op) + { + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->h_)); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, write_some_win32_op* op) + { + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->h_)); + } + + friend + bool asio_handler_is_continuation(write_some_win32_op* op) + { + using boost::asio::asio_handler_is_continuation; + return asio_handler_is_continuation( + std::addressof(op->h_)); + } + + template + friend + void asio_handler_invoke(Function&& f, write_some_win32_op* op) + { + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->h_)); + } +}; + +template +void +write_some_win32_op< + Protocol, Handler, isRequest, Fields, Decorator>:: +operator()() +{ + if(! sr_.is_header_done()) + { + header_ = true; + sr_.split(true); + return detail::async_write_some( + sock_, sr_, std::move(*this)); + } + if(sr_.chunked()) + { + return detail::async_write_some( + sock_, sr_, std::move(*this)); + } + auto& r = sr_.reader_impl(); + boost::detail::winapi::DWORD_ const nNumberOfBytesToWrite = + std::min( + beast::detail::clamp(std::min( + r.body_.last_ - r.pos_, sr_.limit())), + 2147483646); + boost::asio::windows::overlapped_ptr overlapped{ + sock_.get_io_service(), *this}; + auto& ov = *overlapped.get(); + ov.Offset = lowPart(r.pos_); + ov.OffsetHigh = highPart(r.pos_); + auto const bSuccess = ::TransmitFile( + sock_.native_handle(), + sr_.get().body.file_.native_handle(), + nNumberOfBytesToWrite, + 0, + overlapped.get(), + nullptr, + 0); + auto const dwError = ::GetLastError(); + if(! bSuccess && dwError != + boost::detail::winapi::ERROR_IO_PENDING_) + { + // completed immediately + overlapped.complete(error_code{static_cast( + boost::detail::winapi::GetLastError()), + system_category()}, 0); + return; + } + overlapped.release(); +} + +template +void +write_some_win32_op< + Protocol, Handler,isRequest, Fields, Decorator>:: +operator()(error_code ec, std::size_t bytes_transferred) +{ + if(! ec) + { + if(header_) + { + header_ = false; + return (*this)(); + } + auto& r = sr_.reader_impl(); + r.pos_ += bytes_transferred; + BOOST_ASSERT(r.pos_ <= r.body_.last_); + if(r.pos_ >= r.body_.last_) + { + sr_.next(ec, null_lambda{}); + BOOST_ASSERT(! ec); + BOOST_ASSERT(sr_.is_done()); + if(! sr_.keep_alive()) + ec = error::end_of_stream; + } + } + h_(ec); +} + +#endif + +} // detail + +//------------------------------------------------------------------------------ + +template +void +write_some( + boost::asio::basic_stream_socket& sock, + serializer, + Fields, Decorator>& sr, + error_code& ec) +{ + if(! sr.is_header_done()) + { + sr.split(true); + detail::write_some(sock, sr, ec); + if(ec) + return; + return; + } + if(sr.chunked()) + { + detail::write_some(sock, sr, ec); + if(ec) + return; + return; + } + auto& r = sr.reader_impl(); + r.body_.file_.seek(r.pos_, ec); + if(ec) + return; + boost::detail::winapi::DWORD_ const nNumberOfBytesToWrite = + std::min( + beast::detail::clamp(std::min( + r.body_.last_ - r.pos_, sr.limit())), + 2147483646); + auto const bSuccess = ::TransmitFile( + sock.native_handle(), + r.body_.file_.native_handle(), + nNumberOfBytesToWrite, + 0, + nullptr, + nullptr, + 0); + if(! bSuccess) + { + ec.assign(static_cast( + boost::detail::winapi::GetLastError()), + system_category()); + return; + } + r.pos_ += nNumberOfBytesToWrite; + BOOST_ASSERT(r.pos_ <= r.body_.last_); + if(r.pos_ < r.body_.last_) + { + ec.assign(0, ec.category()); + } + else + { + sr.next(ec, detail::null_lambda{}); + BOOST_ASSERT(! ec); + BOOST_ASSERT(sr.is_done()); + if(! sr.keep_alive()) + ec = error::end_of_stream; + } +} + +#if BOOST_ASIO_HAS_WINDOWS_OVERLAPPED_PTR + +template< + class Protocol, + bool isRequest, class Fields, class Decorator, + class WriteHandler> +async_return_type +async_write_some( + boost::asio::basic_stream_socket& sock, + serializer, + Fields, Decorator>& sr, + WriteHandler&& handler) +{ + async_completion init{handler}; + detail::write_some_win32_op, isRequest, Fields, + Decorator>{init.completion_handler, sock, sr}(); + return init.result.get(); +} + +#endif + +} // http +} // beast + +#endif + +#endif diff --git a/include/beast/http/impl/message.ipp b/include/beast/http/impl/message.ipp index b15b89d649..68af2345e9 100644 --- a/include/beast/http/impl/message.ipp +++ b/include/beast/http/impl/message.ipp @@ -9,43 +9,393 @@ #define BEAST_HTTP_IMPL_MESSAGE_IPP #include -#include -#include -#include #include #include -#include +#include #include namespace beast { namespace http { template -void -swap( - header& m1, - header& m2) +template +header:: +header(Arg1&& arg1, ArgN&&... argn) + : Fields(std::forward(arg1), + std::forward(argn)...) { - using std::swap; - swap(m1.version, m2.version); - swap(m1.method, m2.method); - swap(m1.url, m2.url); - swap(m1.fields, m2.fields); +} + +template +inline +verb +header:: +method() const +{ + return method_; +} + +template +void +header:: +method(verb v) +{ + if(v == verb::unknown) + BOOST_THROW_EXCEPTION( + std::invalid_argument{"unknown method"}); + method_ = v; + this->set_method_impl({}); +} + +template +string_view +header:: +method_string() const +{ + if(method_ != verb::unknown) + return to_string(method_); + return this->get_method_impl(); +} + +template +void +header:: +method_string(string_view s) +{ + method_ = string_to_verb(s); + if(method_ != verb::unknown) + this->set_method_impl({}); + else + this->set_method_impl(s); +} + +template +inline +string_view +header:: +target() const +{ + return this->get_target_impl(); +} + +template +inline +void +header:: +target(string_view s) +{ + this->set_target_impl(s); } template void swap( - header& a, - header& b) + header& h1, + header& h2) { using std::swap; - swap(a.version, b.version); - swap(a.status, b.status); - swap(a.reason, b.reason); - swap(a.fields, b.fields); + swap( + static_cast(h1), + static_cast(h2)); + swap(h1.version, h2.version); + swap(h1.method_, h2.method_); } +//------------------------------------------------------------------------------ + +template +template +header:: +header(Arg1&& arg1, ArgN&&... argn) + : Fields(std::forward(arg1), + std::forward(argn)...) +{ +} + +#if 0 +template +template +header:: +header(status result, unsigned version_, Args&&... args) + : Fields(std::forward(args)...) + , version(version_) + , result_(result) +{ +} +#endif + +template +inline +status +header:: +result() const +{ + return int_to_status( + static_cast(result_)); +} + +template +inline +void +header:: +result(status v) +{ + result_ = v; +} + +template +inline +void +header:: +result(unsigned v) +{ + if(v > 999) + BOOST_THROW_EXCEPTION( + std::invalid_argument{ + "invalid status-code"}); + result_ = static_cast(v); +} + +template +inline +unsigned +header:: +result_int() const +{ + return static_cast(result_); +} + +template +string_view +header:: +reason() const +{ + auto const s = this->get_reason_impl(); + if(! s.empty()) + return s; + return obsolete_reason(result_); +} + +template +inline +void +header:: +reason(string_view s) +{ + this->set_reason_impl(s); +} + +template +void +swap( + header& h1, + header& h2) +{ + using std::swap; + swap( + static_cast(h1), + static_cast(h2)); + swap(h1.version, h2.version); + swap(h1.result_, h2.result_); +} + +//------------------------------------------------------------------------------ + +template +template +message:: +message(header_type&& h, BodyArgs&&... body_args) + : header_type(std::move(h)) + , body(std::forward(body_args)...) +{ +} + +template +template +message:: +message(header_type const& h, BodyArgs&&... body_args) + : header_type(h) + , body(std::forward(body_args)...) +{ +} + +template +template +message:: +message(verb method, string_view target, Version version) + : header_type(method, target, version) +{ +} + +template +template +message:: +message(verb method, string_view target, + Version version, BodyArg&& body_arg) + : header_type(method, target, version) + , body(std::forward(body_arg)) +{ +} + +template +template +message:: +message( + verb method, string_view target, Version version, + BodyArg&& body_arg, + FieldsArg&& fields_arg) + : header_type(method, target, version, + std::forward(fields_arg)) + , body(std::forward(body_arg)) +{ +} + +template +template +message:: +message(status result, Version version) + : header_type(result, version) +{ +} + +template +template +message:: +message(status result, Version version, + BodyArg&& body_arg) + : header_type(result, version) + , body(std::forward(body_arg)) +{ +} + +template +template +message:: +message(status result, Version version, + BodyArg&& body_arg, FieldsArg&& fields_arg) + : header_type(result, version, + std::forward(fields_arg)) + , body(std::forward(body_arg)) +{ +} + +template +message:: +message(std::piecewise_construct_t) +{ +} + +template +template +message:: +message(std::piecewise_construct_t, + std::tuple body_args) + : message(std::piecewise_construct, + body_args, + beast::detail::make_index_sequence< + sizeof...(BodyArgs)>{}) +{ +} + +template +template +message:: +message(std::piecewise_construct_t, + std::tuple body_args, + std::tuple fields_args) + : message(std::piecewise_construct, + body_args, + fields_args, + beast::detail::make_index_sequence< + sizeof...(BodyArgs)>{}, + beast::detail::make_index_sequence< + sizeof...(FieldsArgs)>{}) +{ +} + +template +void +message:: +chunked(bool value) +{ + this->set_chunked_impl(value); + this->set_content_length_impl(boost::none); +} + +template +void +message:: +content_length( + boost::optional const& value) +{ + this->set_content_length_impl(value); + this->set_chunked_impl(false); +} + +template +boost::optional +message:: +payload_size() const +{ + return payload_size(detail::is_body_sized{}); +} + +template +void +message:: +prepare_payload(std::true_type) +{ + auto const n = payload_size(); + if(this->method() == verb::trace && (! n || *n > 0)) + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid request body"}); + if(n) + { + if(*n > 0 || + this->method() == verb::options || + this->method() == verb::put || + this->method() == verb::post) + { + this->content_length(n); + } + else + { + this->chunked(false); + } + } + else if(this->version >= 11) + { + this->chunked(true); + } + else + { + this->chunked(false); + } +} + +template +void +message:: +prepare_payload(std::false_type) +{ + auto const n = payload_size(); + if((status_class(this->result()) == status_class::informational || + this->result() == status::no_content || + this->result() == status::not_modified)) + { + if(! n || *n > 0) + // The response body MUST BE empty for this case + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid response body"}); + } + if(n) + this->content_length(n); + else + this->chunked(true); +} + +//------------------------------------------------------------------------------ + template void swap( @@ -53,213 +403,12 @@ swap( message& m2) { using std::swap; - swap(m1.base(), m2.base()); + swap( + static_cast&>(m1), + static_cast&>(m2)); swap(m1.body, m2.body); } -template -bool -is_keep_alive(header const& msg) -{ - BOOST_ASSERT(msg.version == 10 || msg.version == 11); - if(msg.version == 11) - { - if(token_list{msg.fields["Connection"]}.exists("close")) - return false; - return true; - } - if(token_list{msg.fields["Connection"]}.exists("keep-alive")) - return true; - return false; -} - -template -bool -is_upgrade(header const& msg) -{ - BOOST_ASSERT(msg.version == 10 || msg.version == 11); - if(msg.version == 10) - return false; - if(token_list{msg.fields["Connection"]}.exists("upgrade")) - return true; - return false; -} - -namespace detail { - -struct prepare_info -{ - boost::optional connection_value; - boost::optional content_length; -}; - -template -inline -void -prepare_options(prepare_info& pi, - message& msg) -{ - beast::detail::ignore_unused(pi, msg); -} - -template -void -prepare_option(prepare_info& pi, - message& msg, - connection value) -{ - beast::detail::ignore_unused(msg); - pi.connection_value = value; -} - -template< - bool isRequest, class Body, class Fields, - class Opt, class... Opts> -void -prepare_options(prepare_info& pi, - message& msg, - Opt&& opt, Opts&&... opts) -{ - prepare_option(pi, msg, opt); - prepare_options(pi, msg, - std::forward(opts)...); -} - -template -void -prepare_content_length(prepare_info& pi, - message const& msg, - std::true_type) -{ - typename Body::writer w(msg); - // VFALCO This is a design problem! - error_code ec; - w.init(ec); - if(ec) - throw system_error{ec}; - pi.content_length = w.content_length(); -} - -template -void -prepare_content_length(prepare_info& pi, - message const& msg, - std::false_type) -{ - beast::detail::ignore_unused(msg); - pi.content_length = boost::none; -} - -} // detail - -template< - bool isRequest, class Body, class Fields, - class... Options> -void -prepare(message& msg, - Options&&... options) -{ - using beast::detail::make_exception; - - // VFALCO TODO - static_assert(is_Body::value, - "Body requirements not met"); - static_assert(has_writer::value, - "Body has no writer"); - static_assert(is_Writer>::value, - "Writer requirements not met"); - detail::prepare_info pi; - detail::prepare_content_length(pi, msg, - detail::has_content_length{}); - detail::prepare_options(pi, msg, - std::forward(options)...); - - if(msg.fields.exists("Connection")) - throw make_exception( - "prepare called with Connection field set", __FILE__, __LINE__); - - if(msg.fields.exists("Content-Length")) - throw make_exception( - "prepare called with Content-Length field set", __FILE__, __LINE__); - - if(token_list{msg.fields["Transfer-Encoding"]}.exists("chunked")) - throw make_exception( - "prepare called with Transfer-Encoding: chunked set", __FILE__, __LINE__); - - if(pi.connection_value != connection::upgrade) - { - if(pi.content_length) - { - struct set_field - { - void - operator()(message& msg, - detail::prepare_info const& pi) const - { - using beast::detail::ci_equal; - if(*pi.content_length > 0 || - ci_equal(msg.method, "POST")) - { - msg.fields.insert( - "Content-Length", *pi.content_length); - } - } - - void - operator()(message& msg, - detail::prepare_info const& pi) const - { - if((msg.status / 100 ) != 1 && - msg.status != 204 && - msg.status != 304) - { - msg.fields.insert( - "Content-Length", *pi.content_length); - } - } - }; - set_field{}(msg, pi); - } - else if(msg.version >= 11) - { - msg.fields.insert("Transfer-Encoding", "chunked"); - } - } - - auto const content_length = - msg.fields.exists("Content-Length"); - - if(pi.connection_value) - { - switch(*pi.connection_value) - { - case connection::upgrade: - msg.fields.insert("Connection", "upgrade"); - break; - - case connection::keep_alive: - if(msg.version < 11) - { - if(content_length) - msg.fields.insert("Connection", "keep-alive"); - } - break; - - case connection::close: - if(msg.version >= 11) - msg.fields.insert("Connection", "close"); - break; - } - } - - // rfc7230 6.7. - if(msg.version < 11 && token_list{ - msg.fields["Connection"]}.exists("upgrade")) - throw make_exception( - "invalid version for Connection: upgrade", __FILE__, __LINE__); -} - } // http } // beast diff --git a/include/beast/http/impl/parse.ipp b/include/beast/http/impl/parse.ipp deleted file mode 100644 index 17219fc30a..0000000000 --- a/include/beast/http/impl/parse.ipp +++ /dev/null @@ -1,302 +0,0 @@ -// -// 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_HTTP_IMPL_PARSE_IPP_HPP -#define BEAST_HTTP_IMPL_PARSE_IPP_HPP - -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -namespace detail { - -template -class parse_op -{ - struct data - { - bool cont; - Stream& s; - DynamicBuffer& db; - Parser& p; - bool got_some = false; - int state = 0; - - data(Handler& handler, Stream& s_, - DynamicBuffer& sb_, Parser& p_) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , s(s_) - , db(sb_) - , p(p_) - { - BOOST_ASSERT(! p.complete()); - } - }; - - handler_ptr d_; - -public: - parse_op(parse_op&&) = default; - parse_op(parse_op const&) = default; - - template - parse_op(DeducedHandler&& h, Stream& s, Args&&... args) - : d_(std::forward(h), - s, std::forward(args)...) - { - (*this)(error_code{}, 0, false); - } - - void - operator()(error_code ec, - std::size_t bytes_transferred, bool again = true); - - friend - void* asio_handler_allocate( - std::size_t size, parse_op* op) - { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); - } - - friend - void asio_handler_deallocate( - void* p, std::size_t size, parse_op* op) - { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); - } - - friend - bool asio_handler_is_continuation(parse_op* op) - { - return op->d_->cont; - } - - template - friend - void asio_handler_invoke(Function&& f, parse_op* op) - { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); - } -}; - -template -void -parse_op:: -operator()(error_code ec, std::size_t bytes_transferred, bool again) -{ - auto& d = *d_; - d.cont = d.cont || again; - while(d.state != 99) - { - switch(d.state) - { - case 0: - { - // Parse any bytes left over in the buffer - auto const used = - d.p.write(d.db.data(), ec); - if(ec) - { - // call handler - d.state = 99; - d.s.get_io_service().post( - bind_handler(std::move(*this), ec, 0)); - return; - } - if(used > 0) - { - d.got_some = true; - d.db.consume(used); - } - if(d.p.complete()) - { - // call handler - d.state = 99; - d.s.get_io_service().post( - bind_handler(std::move(*this), ec, 0)); - return; - } - // Buffer must be empty, - // otherwise parse should be complete - BOOST_ASSERT(d.db.size() == 0); - d.state = 1; - break; - } - - case 1: - { - // read - d.state = 2; - auto const size = - read_size_helper(d.db, 65536); - BOOST_ASSERT(size > 0); - d.s.async_read_some( - d.db.prepare(size), std::move(*this)); - return; - } - - // got data - case 2: - { - if(ec == boost::asio::error::eof) - { - // If we haven't processed any bytes, - // give the eof to the handler immediately. - if(! d.got_some) - { - // call handler - d.state = 99; - break; - } - // Feed the eof to the parser to complete - // the parse, and call the handler. The - // next call to parse will deliver the eof. - ec = {}; - d.p.write_eof(ec); - BOOST_ASSERT(ec || d.p.complete()); - // call handler - d.state = 99; - break; - } - if(ec) - { - // call handler - d.state = 99; - break; - } - BOOST_ASSERT(bytes_transferred > 0); - d.db.commit(bytes_transferred); - auto const used = d.p.write(d.db.data(), ec); - if(ec) - { - // call handler - d.state = 99; - break; - } - // The parser must either consume - // bytes or generate an error. - BOOST_ASSERT(used > 0); - d.got_some = true; - d.db.consume(used); - if(d.p.complete()) - { - // call handler - d.state = 99; - break; - } - // If the parse is not complete, - // all input must be consumed. - BOOST_ASSERT(used == bytes_transferred); - d.state = 1; - break; - } - } - } - d_.invoke(ec); -} - -} // detail - -//------------------------------------------------------------------------------ - -template -void -parse(SyncReadStream& stream, - DynamicBuffer& dynabuf, Parser& parser) -{ - static_assert(is_SyncReadStream::value, - "SyncReadStream requirements not met"); - static_assert(is_DynamicBuffer::value, - "DynamicBuffer requirements not met"); - static_assert(is_Parser::value, - "Parser requirements not met"); - error_code ec; - parse(stream, dynabuf, parser, ec); - if(ec) - throw system_error{ec}; -} - -template -void -parse(SyncReadStream& stream, DynamicBuffer& dynabuf, - Parser& parser, error_code& ec) -{ - static_assert(is_SyncReadStream::value, - "SyncReadStream requirements not met"); - static_assert(is_DynamicBuffer::value, - "DynamicBuffer requirements not met"); - static_assert(is_Parser::value, - "Parser requirements not met"); - bool got_some = false; - for(;;) - { - auto used = - parser.write(dynabuf.data(), ec); - if(ec) - return; - dynabuf.consume(used); - if(used > 0) - got_some = true; - if(parser.complete()) - break; - dynabuf.commit(stream.read_some( - dynabuf.prepare(read_size_helper( - dynabuf, 65536)), ec)); - if(ec && ec != boost::asio::error::eof) - return; - if(ec == boost::asio::error::eof) - { - if(! got_some) - return; - // Caller will see eof on next read. - ec = {}; - parser.write_eof(ec); - if(ec) - return; - BOOST_ASSERT(parser.complete()); - break; - } - } -} - -template -typename async_completion< - ReadHandler, void(error_code)>::result_type -async_parse(AsyncReadStream& stream, - DynamicBuffer& dynabuf, Parser& parser, ReadHandler&& handler) -{ - static_assert(is_AsyncReadStream::value, - "AsyncReadStream requirements not met"); - static_assert(is_DynamicBuffer::value, - "DynamicBuffer requirements not met"); - static_assert(is_Parser::value, - "Parser requirements not met"); - beast::async_completion completion{handler}; - detail::parse_op{ - completion.handler, stream, dynabuf, parser}; - return completion.result.get(); -} - -} // http -} // beast - -#endif diff --git a/include/beast/http/impl/parse_error.ipp b/include/beast/http/impl/parse_error.ipp deleted file mode 100644 index d7943a0e9b..0000000000 --- a/include/beast/http/impl/parse_error.ipp +++ /dev/null @@ -1,105 +0,0 @@ -// -// 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_HTTP_IMPL_PARSE_ERROR_IPP -#define BEAST_HTTP_IMPL_PARSE_ERROR_IPP - -namespace boost { -namespace system { -template<> -struct is_error_code_enum -{ - static bool const value = true; -}; -} // system -} // boost - -namespace beast { -namespace http { -namespace detail { - -class parse_error_category : public error_category -{ -public: - const char* - name() const noexcept override - { - return "http"; - } - - std::string - message(int ev) const override - { - switch(static_cast(ev)) - { - case parse_error::connection_closed: return "data after Connection close"; - case parse_error::bad_method: return "bad method"; - case parse_error::bad_uri: return "bad request-target"; - case parse_error::bad_version: return "bad HTTP-Version"; - case parse_error::bad_crlf: return "missing CRLF"; - case parse_error::bad_status: return "bad status-code"; - case parse_error::bad_reason: return "bad reason-phrase"; - case parse_error::bad_field: return "bad field token"; - case parse_error::bad_value: return "bad field-value"; - case parse_error::bad_content_length: return "bad Content-Length"; - case parse_error::illegal_content_length: return "illegal Content-Length with chunked Transfer-Encoding"; - case parse_error::invalid_chunk_size: return "invalid chunk size"; - case parse_error::invalid_ext_name: return "invalid ext name"; - case parse_error::invalid_ext_val: return "invalid ext val"; - case parse_error::header_too_big: return "header size limit exceeded"; - case parse_error::body_too_big: return "body size limit exceeded"; - default: - case parse_error::short_read: return "unexpected end of data"; - } - } - - error_condition - default_error_condition(int ev) const noexcept override - { - return error_condition{ev, *this}; - } - - bool - equivalent(int ev, - error_condition const& condition - ) const noexcept override - { - return condition.value() == ev && - &condition.category() == this; - } - - bool - equivalent(error_code const& error, int ev) const noexcept override - { - return error.value() == ev && - &error.category() == this; - } -}; - -inline -error_category const& -get_parse_error_category() -{ - static parse_error_category const cat{}; - return cat; -} - -} // detail - -inline -error_code -make_error_code(parse_error ev) -{ - return error_code{ - static_cast::type>(ev), - detail::get_parse_error_category()}; -} - -} // http -} // beast - -#endif diff --git a/include/beast/http/impl/parser.ipp b/include/beast/http/impl/parser.ipp new file mode 100644 index 0000000000..73bacd9ad7 --- /dev/null +++ b/include/beast/http/impl/parser.ipp @@ -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) +// + +#ifndef BEAST_HTTP_IMPL_PARSER_IPP +#define BEAST_HTTP_IMPL_PARSER_IPP + +#include +#include + +namespace beast { +namespace http { + +template +parser:: +parser() + : wr_(m_) +{ +} + +template +template +parser:: +parser(Arg1&& arg1, ArgN&&... argn) + : m_(std::forward(arg1), + std::forward(argn)...) + , wr_(m_) +{ +} + +template +template +parser:: +parser(parser&& p, + Args&&... args) + : base_type(std::move(p)) + , m_(p.release(), std::forward(args)...) + , wr_(m_) +{ + if(wr_inited_) + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "moved-from parser has a body"}); +} + +} // http +} // beast + +#endif diff --git a/include/beast/http/impl/read.ipp b/include/beast/http/impl/read.ipp index 3bcf27c7b8..08e7c552d3 100644 --- a/include/beast/http/impl/read.ipp +++ b/include/beast/http/impl/read.ipp @@ -8,379 +8,767 @@ #ifndef BEAST_HTTP_IMPL_READ_IPP_HPP #define BEAST_HTTP_IMPL_READ_IPP_HPP -#include -#include -#include -#include +#include +#include +#include +#include #include -#include #include -#include +#include +#include +#include +#include +#include #include +#include +#include +#include namespace beast { namespace http { namespace detail { +//------------------------------------------------------------------------------ + template -class read_header_op + bool isRequest, class Derived, class Handler> +class read_some_op { - using parser_type = - header_parser_v1; - - using message_type = - header; - - struct data - { - bool cont; - Stream& s; - DynamicBuffer& db; - message_type& m; - parser_type p; - bool started = false; - int state = 0; - - data(Handler& handler, Stream& s_, - DynamicBuffer& sb_, message_type& m_) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , s(s_) - , db(sb_) - , m(m_) - { - } - }; - - handler_ptr d_; + int state_ = 0; + Stream& s_; + DynamicBuffer& b_; + basic_parser& p_; + boost::optional mb_; + Handler h_; public: - read_header_op(read_header_op&&) = default; - read_header_op(read_header_op const&) = default; + read_some_op(read_some_op&&) = default; + read_some_op(read_some_op const&) = default; - template - read_header_op( - DeducedHandler&& h, Stream& s, Args&&... args) - : d_(std::forward(h), - s, std::forward(args)...) + template + read_some_op(DeducedHandler&& h, Stream& s, + DynamicBuffer& b, basic_parser& p) + : s_(s) + , b_(b) + , p_(p) + , h_(std::forward(h)) { - (*this)(error_code{}, false); } void - operator()(error_code ec, bool again = true); + operator()(error_code ec, std::size_t bytes_transferred); friend void* asio_handler_allocate( - std::size_t size, read_header_op* op) + std::size_t size, read_some_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->h_)); } friend void asio_handler_deallocate( - void* p, std::size_t size, read_header_op* op) + void* p, std::size_t size, read_some_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->h_)); } friend - bool asio_handler_is_continuation(read_header_op* op) + bool asio_handler_is_continuation(read_some_op* op) { - return op->d_->cont; + using boost::asio::asio_handler_is_continuation; + return op->state_ >= 2 ? true : + asio_handler_is_continuation( + std::addressof(op->h_)); } template friend - void asio_handler_invoke(Function&& f, read_header_op* op) + void asio_handler_invoke(Function&& f, read_some_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->h_)); } }; template + bool isRequest, class Derived, class Handler> void -read_header_op:: -operator()(error_code ec, bool again) +read_some_op:: +operator()(error_code ec, std::size_t bytes_transferred) { - auto& d = *d_; - d.cont = d.cont || again; - while(! ec && d.state != 99) + switch(state_) { - switch(d.state) + case 0: + state_ = 1; + if(b_.size() == 0) + goto do_read; + goto do_parse; + + case 1: + state_ = 2; + case 2: + if(ec == boost::asio::error::eof) { - case 0: - d.state = 1; - async_parse(d.s, d.db, d.p, std::move(*this)); - return; - - case 1: - // call handler - d.state = 99; - d.m = d.p.release(); - break; + BOOST_ASSERT(bytes_transferred == 0); + if(p_.got_some()) + { + // caller sees EOF on next read + ec.assign(0, ec.category()); + p_.put_eof(ec); + if(ec) + goto upcall; + BOOST_ASSERT(p_.is_done()); + goto upcall; + } + ec = error::end_of_stream; + goto upcall; } + if(ec) + goto upcall; + b_.commit(bytes_transferred); + + do_parse: + b_.consume(p_.put(b_.data(), ec)); + if(! ec || ec != http::error::need_more) + goto do_upcall; + ec.assign(0, ec.category()); + + do_read: + try + { + mb_.emplace(b_.prepare( + read_size_or_throw(b_, 65536))); + } + catch(std::length_error const&) + { + ec = error::buffer_overflow; + goto do_upcall; + } + return s_.async_read_some(*mb_, std::move(*this)); + + do_upcall: + if(state_ >= 2) + goto upcall; + state_ = 3; + return s_.get_io_service().post( + bind_handler(std::move(*this), ec, 0)); + + case 3: + break; } - d_.invoke(ec); -} - -} // detail - -template -void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - header& msg) -{ - static_assert(is_SyncReadStream::value, - "SyncReadStream requirements not met"); - static_assert(is_DynamicBuffer::value, - "DynamicBuffer requirements not met"); - error_code ec; - beast::http::read(stream, dynabuf, msg, ec); - if(ec) - throw system_error{ec}; -} - -template -void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - header& m, - error_code& ec) -{ - static_assert(is_SyncReadStream::value, - "SyncReadStream requirements not met"); - static_assert(is_DynamicBuffer::value, - "DynamicBuffer requirements not met"); - header_parser_v1 p; - beast::http::parse(stream, dynabuf, p, ec); - if(ec) - return; - BOOST_ASSERT(p.complete()); - m = p.release(); -} - -template -typename async_completion< - ReadHandler, void(error_code)>::result_type -async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf, - header& m, - ReadHandler&& handler) -{ - static_assert(is_AsyncReadStream::value, - "AsyncReadStream requirements not met"); - static_assert(is_DynamicBuffer::value, - "DynamicBuffer requirements not met"); - beast::async_completion completion{handler}; - detail::read_header_op{completion.handler, - stream, dynabuf, m}; - return completion.result.get(); +upcall: + h_(ec); } //------------------------------------------------------------------------------ -namespace detail { +struct parser_is_done +{ + template + bool + operator()(basic_parser< + isRequest, Derived> const& p) const + { + return p.is_done(); + } +}; + +struct parser_is_header_done +{ + template + bool + operator()(basic_parser< + isRequest, Derived> const& p) const + { + return p.is_header_done(); + } +}; template class read_op { - using parser_type = - parser_v1; - - using message_type = - message; - - struct data - { - bool cont; - Stream& s; - DynamicBuffer& db; - message_type& m; - parser_type p; - bool started = false; - int state = 0; - - data(Handler& handler, Stream& s_, - DynamicBuffer& sb_, message_type& m_) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , s(s_) - , db(sb_) - , m(m_) - { - } - }; - - handler_ptr d_; + int state_ = 0; + Stream& s_; + DynamicBuffer& b_; + basic_parser& p_; + Handler h_; public: read_op(read_op&&) = default; read_op(read_op const&) = default; - template - read_op(DeducedHandler&& h, Stream& s, Args&&... args) - : d_(std::forward(h), - s, std::forward(args)...) + template + read_op(DeducedHandler&& h, Stream& s, + DynamicBuffer& b, basic_parser& p) + : s_(s) + , b_(b) + , p_(p) + , h_(std::forward(h)) { - (*this)(error_code{}, false); } void - operator()(error_code ec, bool again = true); + operator()(error_code ec); friend void* asio_handler_allocate( std::size_t size, read_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->h_)); } friend void asio_handler_deallocate( void* p, std::size_t size, read_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->h_)); } friend bool asio_handler_is_continuation(read_op* op) { - return op->d_->cont; + using boost::asio::asio_handler_is_continuation; + return op->state_ >= 3 ? true : + asio_handler_is_continuation( + std::addressof(op->h_)); } template friend void asio_handler_invoke(Function&& f, read_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->h_)); } }; template void -read_op:: -operator()(error_code ec, bool again) +read_op:: +operator()(error_code ec) +{ + switch(state_) + { + case 0: + if(Condition{}(p_)) + { + state_ = 1; + return s_.get_io_service().post( + bind_handler(std::move(*this), ec)); + } + state_ = 2; + + do_read: + return async_read_some( + s_, b_, p_, std::move(*this)); + + case 1: + goto upcall; + + case 2: + case 3: + if(ec) + goto upcall; + if(Condition{}(p_)) + goto upcall; + state_ = 3; + goto do_read; + } +upcall: + h_(ec); +} + +//------------------------------------------------------------------------------ + +template +class read_msg_op +{ + using parser_type = + parser; + + using message_type = + typename parser_type::value_type; + + struct data + { + int state = 0; + Stream& s; + DynamicBuffer& b; + message_type& m; + parser_type p; + + data(Handler&, Stream& s_, + DynamicBuffer& b_, message_type& m_) + : s(s_) + , b(b_) + , m(m_) + , p(std::move(m)) + { + p.eager(true); + } + }; + + handler_ptr d_; + +public: + read_msg_op(read_msg_op&&) = default; + read_msg_op(read_msg_op const&) = default; + + template + read_msg_op(DeducedHandler&& h, Stream& s, Args&&... args) + : d_(std::forward(h), + s, std::forward(args)...) + { + } + + void + operator()(error_code ec); + + friend + void* asio_handler_allocate( + std::size_t size, read_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, read_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(read_msg_op* op) + { + using boost::asio::asio_handler_is_continuation; + return op->d_->state >= 2 ? true : + asio_handler_is_continuation( + std::addressof(op->d_.handler())); + } + + template + friend + void asio_handler_invoke(Function&& f, read_msg_op* op) + { + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->d_.handler())); + } +}; + +template +void +read_msg_op:: +operator()(error_code ec) { auto& d = *d_; - d.cont = d.cont || again; - while(! ec && d.state != 99) + switch(d.state) { - switch(d.state) - { - case 0: - d.state = 1; - async_parse(d.s, d.db, d.p, std::move(*this)); - return; + case 0: + d.state = 1; - case 1: - // call handler - d.state = 99; + do_read: + return async_read_some( + d.s, d.b, d.p, std::move(*this)); + + case 1: + case 2: + if(ec) + goto upcall; + if(d.p.is_done()) + { d.m = d.p.release(); - break; + goto upcall; } + d.state = 2; + goto do_read; } +upcall: d_.invoke(ec); } } // detail -template +//------------------------------------------------------------------------------ + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - message& msg) +read_some( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser) { - static_assert(is_SyncReadStream::value, + static_assert(is_sync_read_stream::value, "SyncReadStream requirements not met"); - static_assert(is_DynamicBuffer::value, + static_assert(is_dynamic_buffer::value, "DynamicBuffer requirements not met"); - static_assert(is_Body::value, - "Body requirements not met"); - static_assert(has_reader::value, - "Body has no reader"); - static_assert(is_Reader>::value, - "Reader requirements not met"); + BOOST_ASSERT(! parser.is_done()); error_code ec; - beast::http::read(stream, dynabuf, msg, ec); + read_some(stream, buffer, parser, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); } -template +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - message& m, - error_code& ec) +read_some( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + error_code& ec) { - static_assert(is_SyncReadStream::value, + static_assert(is_sync_read_stream::value, "SyncReadStream requirements not met"); - static_assert(is_DynamicBuffer::value, + static_assert(is_dynamic_buffer::value, "DynamicBuffer requirements not met"); - static_assert(is_Body::value, + BOOST_ASSERT(! parser.is_done()); + if(buffer.size() == 0) + goto do_read; + for(;;) + { + // invoke parser + buffer.consume(parser.put(buffer.data(), ec)); + if(! ec) + break; + if(ec != http::error::need_more) + break; + do_read: + boost::optional b; + try + { + b.emplace(buffer.prepare( + read_size_or_throw(buffer, 65536))); + } + catch(std::length_error const&) + { + ec = error::buffer_overflow; + return; + } + auto const bytes_transferred = + stream.read_some(*b, ec); + if(ec == boost::asio::error::eof) + { + BOOST_ASSERT(bytes_transferred == 0); + if(parser.got_some()) + { + // caller sees EOF on next read + parser.put_eof(ec); + if(ec) + break; + BOOST_ASSERT(parser.is_done()); + break; + } + ec = error::end_of_stream; + break; + } + if(ec) + break; + buffer.commit(bytes_transferred); + } +} + +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived, + class ReadHandler> +async_return_type< + ReadHandler, void(error_code)> +async_read_some( + AsyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + ReadHandler&& handler) +{ + static_assert(is_async_read_stream::value, + "AsyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + BOOST_ASSERT(! parser.is_done()); + async_completion init{handler}; + detail::read_some_op>{ + init.completion_handler, stream, buffer, parser}( + error_code{}, 0); + return init.result.get(); +} + +//------------------------------------------------------------------------------ + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +void +read_header( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser) +{ + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + error_code ec; + read_header(stream, buffer, parser, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +void +read_header( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + error_code& ec) +{ + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + parser.eager(false); + if(parser.is_header_done()) + { + ec.assign(0, ec.category()); + return; + } + do + { + read_some(stream, buffer, parser, ec); + if(ec) + return; + } + while(! parser.is_header_done()); +} + +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived, + class ReadHandler> +async_return_type +async_read_header( + AsyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + ReadHandler&& handler) +{ + static_assert(is_async_read_stream::value, + "AsyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + parser.eager(false); + async_completion init{handler}; + detail::read_op>{ + init.completion_handler, stream, buffer, parser}( + error_code{}); + return init.result.get(); +} + +//------------------------------------------------------------------------------ + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +void +read( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser) +{ + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + error_code ec; + read(stream, buffer, parser, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +void +read( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + error_code& ec) +{ + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + parser.eager(true); + if(parser.is_done()) + { + ec.assign(0, ec.category()); + return; + } + do + { + read_some(stream, buffer, parser, ec); + if(ec) + return; + } + while(! parser.is_done()); +} + +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived, + class ReadHandler> +async_return_type +async_read( + AsyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + ReadHandler&& handler) +{ + static_assert(is_async_read_stream::value, + "AsyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + parser.eager(true); + async_completion init{handler}; + detail::read_op>{ + init.completion_handler, stream, buffer, parser}( + error_code{}); + return init.result.get(); +} + +//------------------------------------------------------------------------------ + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Body, class Allocator> +void +read( + SyncReadStream& stream, + DynamicBuffer& buffer, + message>& msg) +{ + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + static_assert(is_body::value, "Body requirements not met"); - static_assert(has_reader::value, - "Body has no reader"); - static_assert(is_Reader>::value, - "Reader requirements not met"); - parser_v1 p; - beast::http::parse(stream, dynabuf, p, ec); + static_assert(is_body_writer::value, + "BodyWriter requirements not met"); + error_code ec; + read(stream, buffer, msg, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Body, class Allocator> +void +read( + SyncReadStream& stream, + DynamicBuffer& buffer, + message>& msg, + error_code& ec) +{ + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_writer::value, + "BodyWriter requirements not met"); + parser p{std::move(msg)}; + p.eager(true); + read(stream, buffer, p.base(), ec); if(ec) return; - BOOST_ASSERT(p.complete()); - m = p.release(); + msg = p.release(); } -template -typename async_completion< - ReadHandler, void(error_code)>::result_type -async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf, - message& m, - ReadHandler&& handler) +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Body, class Allocator, + class ReadHandler> +async_return_type +async_read( + AsyncReadStream& stream, + DynamicBuffer& buffer, + message>& msg, + ReadHandler&& handler) { - static_assert(is_AsyncReadStream::value, + static_assert(is_async_read_stream::value, "AsyncReadStream requirements not met"); - static_assert(is_DynamicBuffer::value, + static_assert(is_dynamic_buffer::value, "DynamicBuffer requirements not met"); - static_assert(is_Body::value, + static_assert(is_body::value, "Body requirements not met"); - static_assert(has_reader::value, - "Body has no reader"); - static_assert(is_Reader>::value, - "Reader requirements not met"); - beast::async_completion completion{handler}; - detail::read_op{completion.handler, - stream, dynabuf, m}; - return completion.result.get(); + static_assert(is_body_writer::value, + "BodyWriter requirements not met"); + async_completion init{handler}; + detail::read_msg_op>{ + init.completion_handler, stream, buffer, msg}( + error_code{}); + return init.result.get(); } } // http diff --git a/include/beast/http/impl/rfc7230.ipp b/include/beast/http/impl/rfc7230.ipp index 5ad65c28d1..bce0ec856b 100644 --- a/include/beast/http/impl/rfc7230.ipp +++ b/include/beast/http/impl/rfc7230.ipp @@ -8,7 +8,6 @@ #ifndef BEAST_HTTP_IMPL_RFC7230_IPP #define BEAST_HTTP_IMPL_RFC7230_IPP -#include #include #include @@ -17,7 +16,7 @@ namespace http { class param_list::const_iterator { - using iter_type = boost::string_ref::const_iterator; + using iter_type = string_view::const_iterator; std::string s_; detail::param_iter pi_; @@ -87,7 +86,7 @@ private: template static std::string - unquote(boost::string_ref const& sr); + unquote(string_view sr); template void @@ -133,7 +132,7 @@ cend() const -> template std::string param_list::const_iterator:: -unquote(boost::string_ref const& sr) +unquote(string_view sr) { std::string s; s.reserve(sr.size()); @@ -165,7 +164,7 @@ increment() pi_.v.second.front() == '"') { s_ = unquote(pi_.v.second); - pi_.v.second = boost::string_ref{ + pi_.v.second = string_view{ s_.data(), s_.size()}; } } @@ -291,7 +290,7 @@ find(T const& s) -> return std::find_if(begin(), end(), [&s](value_type const& v) { - return beast::detail::ci_equal(s, v.first); + return iequals(s, v.first); }); } @@ -346,7 +345,7 @@ increment() if(! detail::is_tchar(*it_)) break; } - v_.first = boost::string_ref{&*p0, + v_.first = string_view{&*p0, static_cast(it_ - p0)}; detail::param_iter pi; pi.it = it_; @@ -358,7 +357,7 @@ increment() if(pi.empty()) break; } - v_.second = param_list{boost::string_ref{&*it_, + v_.second = param_list{string_view{&*it_, static_cast(pi.it - it_)}}; it_ = pi.it; return; @@ -518,7 +517,7 @@ increment() if(! detail::is_tchar(*it_)) break; } - v_ = boost::string_ref{&*p0, + v_ = string_view{&*p0, static_cast(it_ - p0)}; return; } @@ -537,11 +536,31 @@ exists(T const& s) return std::find_if(begin(), end(), [&s](value_type const& v) { - return beast::detail::ci_equal(s, v); + return iequals(s, v); } ) != end(); } +template +bool +validate_list(detail::basic_parsed_list< + Policy> const& list) +{ + auto const last = list.end(); + auto it = list.begin(); + if(it.error()) + return false; + while(it != last) + { + ++it; + if(it.error()) + return false; + if(it == last) + break; + } + return true; +} + } // http } // beast diff --git a/include/beast/http/impl/serializer.ipp b/include/beast/http/impl/serializer.ipp new file mode 100644 index 0000000000..cc288f683a --- /dev/null +++ b/include/beast/http/impl/serializer.ipp @@ -0,0 +1,496 @@ +// +// 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_HTTP_IMPL_SERIALIZER_IPP +#define BEAST_HTTP_IMPL_SERIALIZER_IPP + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +template +void +serializer:: +frdinit(std::true_type) +{ + frd_.emplace(m_, m_.version, m_.method()); +} + +template +void +serializer:: +frdinit(std::false_type) +{ + frd_.emplace(m_, m_.version, m_.result_int()); +} + +template +template +inline +void +serializer:: +do_visit(error_code& ec, Visit& visit) +{ + // VFALCO work-around for missing variant::emplace + pv_.~variant(); + new(&pv_) decltype(pv_){ + T1{limit_, boost::get(v_)}}; + visit(ec, beast::detail::make_buffers_ref( + boost::get(pv_))); +} + +//------------------------------------------------------------------------------ + +template +serializer:: +serializer(value_type& m) + : m_(m) + , rd_(m_) +{ +} + +template +serializer:: +serializer(value_type& m, ChunkDecorator const& d) + : m_(m) + , rd_(m_) + , d_(d) +{ +} + +template +template +void +serializer:: +next(error_code& ec, Visit&& visit) +{ + using boost::asio::buffer_size; + using beast::detail::make_buffers_ref; + switch(s_) + { + case do_construct: + { + frdinit(std::integral_constant{}); + keep_alive_ = m_.keep_alive(); + chunked_ = m_.chunked(); + if(chunked_) + goto go_init_c; + s_ = do_init; + BEAST_FALLTHROUGH; + } + + case do_init: + { + rd_.init(ec); + if(ec) + return; + if(split_) + goto go_header_only; + auto result = rd_.get(ec); + if(ec == error::need_more) + goto go_header_only; + if(ec) + return; + if(! result) + goto go_header_only; + more_ = result->second; + v_ = cb2_t{ + boost::in_place_init, + frd_->get(), + result->first}; + s_ = do_header; + BEAST_FALLTHROUGH; + } + + case do_header: + do_visit(ec, visit); + break; + + go_header_only: + v_ = cb1_t{frd_->get()}; + s_ = do_header_only; + case do_header_only: + do_visit(ec, visit); + break; + + case do_body: + s_ = do_body + 1; + BEAST_FALLTHROUGH; + + case do_body + 1: + { + auto result = rd_.get(ec); + if(ec) + return; + if(! result) + goto go_complete; + more_ = result->second; + v_ = cb3_t{result->first}; + s_ = do_body + 2; + BEAST_FALLTHROUGH; + } + + case do_body + 2: + do_visit(ec, visit); + break; + + //---------------------------------------------------------------------- + + go_init_c: + s_ = do_init_c; + case do_init_c: + { + rd_.init(ec); + if(ec) + return; + if(split_) + goto go_header_only_c; + auto result = rd_.get(ec); + if(ec == error::need_more) + goto go_header_only_c; + if(ec) + return; + if(! result) + goto go_header_only_c; + more_ = result->second; + #ifndef BEAST_NO_BIG_VARIANTS + if(! more_) + { + // do it all in one buffer + v_ = cb7_t{ + boost::in_place_init, + frd_->get(), + detail::chunk_header{ + buffer_size(result->first)}, + [&]() + { + auto sv = d_(result->first); + return boost::asio::const_buffers_1{ + sv.data(), sv.size()}; + + }(), + detail::chunk_crlf(), + result->first, + detail::chunk_crlf(), + detail::chunk_final(), + [&]() + { + auto sv = d_( + boost::asio::null_buffers{}); + return boost::asio::const_buffers_1{ + sv.data(), sv.size()}; + + }(), + detail::chunk_crlf()}; + goto go_all_c; + } + #endif + v_ = cb4_t{ + boost::in_place_init, + frd_->get(), + detail::chunk_header{ + buffer_size(result->first)}, + [&]() + { + auto sv = d_(result->first); + return boost::asio::const_buffers_1{ + sv.data(), sv.size()}; + + }(), + detail::chunk_crlf(), + result->first, + detail::chunk_crlf()}; + s_ = do_header_c; + BEAST_FALLTHROUGH; + } + + case do_header_c: + do_visit(ec, visit); + break; + + go_header_only_c: + v_ = cb1_t{frd_->get()}; + s_ = do_header_only_c; + case do_header_only_c: + do_visit(ec, visit); + break; + + case do_body_c: + s_ = do_body_c + 1; + BEAST_FALLTHROUGH; + + case do_body_c + 1: + { + auto result = rd_.get(ec); + if(ec) + return; + if(! result) + goto go_final_c; + more_ = result->second; + #ifndef BEAST_NO_BIG_VARIANTS + if(! more_) + { + // do it all in one buffer + v_ = cb6_t{ + boost::in_place_init, + detail::chunk_header{ + buffer_size(result->first)}, + [&]() + { + auto sv = d_(result->first); + return boost::asio::const_buffers_1{ + sv.data(), sv.size()}; + + }(), + detail::chunk_crlf(), + result->first, + detail::chunk_crlf(), + detail::chunk_final(), + [&]() + { + auto sv = d_( + boost::asio::null_buffers{}); + return boost::asio::const_buffers_1{ + sv.data(), sv.size()}; + + }(), + detail::chunk_crlf()}; + goto go_body_final_c; + } + #endif + v_ = cb5_t{ + boost::in_place_init, + detail::chunk_header{ + buffer_size(result->first)}, + [&]() + { + auto sv = d_(result->first); + return boost::asio::const_buffers_1{ + sv.data(), sv.size()}; + + }(), + detail::chunk_crlf(), + result->first, + detail::chunk_crlf()}; + s_ = do_body_c + 2; + BEAST_FALLTHROUGH; + } + + case do_body_c + 2: + do_visit(ec, visit); + break; + +#ifndef BEAST_NO_BIG_VARIANTS + go_body_final_c: + s_ = do_body_final_c; + case do_body_final_c: + do_visit(ec, visit); + break; + + go_all_c: + s_ = do_all_c; + case do_all_c: + do_visit(ec, visit); + break; +#endif + + go_final_c: + case do_final_c: + v_ = cb8_t{ + boost::in_place_init, + detail::chunk_final(), + [&]() + { + auto sv = d_( + boost::asio::null_buffers{}); + return boost::asio::const_buffers_1{ + sv.data(), sv.size()}; + + }(), + detail::chunk_crlf()}; + s_ = do_final_c + 1; + BEAST_FALLTHROUGH; + + case do_final_c + 1: + do_visit(ec, visit); + break; + + //---------------------------------------------------------------------- + + default: + case do_complete: + BOOST_ASSERT(false); + break; + + go_complete: + s_ = do_complete; + break; + } +} + +template +void +serializer:: +consume(std::size_t n) +{ + using boost::asio::buffer_size; + switch(s_) + { + case do_header: + BOOST_ASSERT(n <= buffer_size( + boost::get(v_))); + boost::get(v_).consume(n); + if(buffer_size(boost::get(v_)) > 0) + break; + header_done_ = true; + v_ = boost::blank{}; + if(! more_) + goto go_complete; + s_ = do_body + 1; + break; + + case do_header_only: + BOOST_ASSERT(n <= buffer_size( + boost::get(v_))); + boost::get(v_).consume(n); + if(buffer_size(boost::get(v_)) > 0) + break; + frd_ = boost::none; + header_done_ = true; + if(! split_) + goto go_complete; + s_ = do_body; + break; + + case do_body + 2: + { + BOOST_ASSERT(n <= buffer_size( + boost::get(v_))); + boost::get(v_).consume(n); + if(buffer_size(boost::get(v_)) > 0) + break; + v_ = boost::blank{}; + if(! more_) + goto go_complete; + s_ = do_body + 1; + break; + } + + //---------------------------------------------------------------------- + + case do_header_c: + BOOST_ASSERT(n <= buffer_size( + boost::get(v_))); + boost::get(v_).consume(n); + if(buffer_size(boost::get(v_)) > 0) + break; + header_done_ = true; + v_ = boost::blank{}; + if(more_) + s_ = do_body_c + 1; + else + s_ = do_final_c; + break; + + case do_header_only_c: + { + BOOST_ASSERT(n <= buffer_size( + boost::get(v_))); + boost::get(v_).consume(n); + if(buffer_size(boost::get(v_)) > 0) + break; + frd_ = boost::none; + header_done_ = true; + if(! split_) + { + s_ = do_final_c; + break; + } + s_ = do_body_c; + break; + } + + case do_body_c + 2: + BOOST_ASSERT(n <= buffer_size( + boost::get(v_))); + boost::get(v_).consume(n); + if(buffer_size(boost::get(v_)) > 0) + break; + v_ = boost::blank{}; + if(more_) + s_ = do_body_c + 1; + else + s_ = do_final_c; + break; + +#ifndef BEAST_NO_BIG_VARIANTS + case do_body_final_c: + { + auto& b = boost::get(v_); + BOOST_ASSERT(n <= buffer_size(b)); + b.consume(n); + if(buffer_size(b) > 0) + break; + v_ = boost::blank{}; + s_ = do_complete; + break; + } + + case do_all_c: + { + auto& b = boost::get(v_); + BOOST_ASSERT(n <= buffer_size(b)); + b.consume(n); + if(buffer_size(b) > 0) + break; + header_done_ = true; + v_ = boost::blank{}; + s_ = do_complete; + break; + } +#endif + + case do_final_c + 1: + BOOST_ASSERT(buffer_size( + boost::get(v_))); + boost::get(v_).consume(n); + if(buffer_size(boost::get(v_)) > 0) + break; + v_ = boost::blank{}; + goto go_complete; + + //---------------------------------------------------------------------- + + default: + BOOST_ASSERT(false); + case do_complete: + break; + + go_complete: + s_ = do_complete; + break; + } +} + +} // http +} // beast + +#endif diff --git a/include/beast/http/impl/status.ipp b/include/beast/http/impl/status.ipp new file mode 100644 index 0000000000..671547c92c --- /dev/null +++ b/include/beast/http/impl/status.ipp @@ -0,0 +1,248 @@ +// +// 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_HTTP_IMPL_STATUS_IPP +#define BEAST_HTTP_IMPL_STATUS_IPP + +#include +#include + +namespace beast { +namespace http { +namespace detail { + +template +status +int_to_status(unsigned v) +{ + switch(static_cast(v)) + { + // 1xx + case status::continue_: + case status::switching_protocols: + case status::processing: + BEAST_FALLTHROUGH; + + // 2xx + case status::ok: + case status::created: + case status::accepted: + case status::non_authoritative_information: + case status::no_content: + case status::reset_content: + case status::partial_content: + case status::multi_status: + case status::already_reported: + case status::im_used: + BEAST_FALLTHROUGH; + + // 3xx + case status::multiple_choices: + case status::moved_permanently: + case status::found: + case status::see_other: + case status::not_modified: + case status::use_proxy: + case status::temporary_redirect: + case status::permanent_redirect: + BEAST_FALLTHROUGH; + + // 4xx + case status::bad_request: + case status::unauthorized: + case status::payment_required: + case status::forbidden: + case status::not_found: + case status::method_not_allowed: + case status::not_acceptable: + case status::proxy_authentication_required: + case status::request_timeout: + case status::conflict: + case status::gone: + case status::length_required: + case status::precondition_failed: + case status::payload_too_large: + case status::uri_too_long: + case status::unsupported_media_type: + case status::range_not_satisfiable: + case status::expectation_failed: + case status::misdirected_request: + case status::unprocessable_entity: + case status::locked: + case status::failed_dependency: + case status::upgrade_required: + case status::precondition_required: + case status::too_many_requests: + case status::request_header_fields_too_large: + case status::connection_closed_without_response: + case status::unavailable_for_legal_reasons: + case status::client_closed_request: + BEAST_FALLTHROUGH; + + // 5xx + case status::internal_server_error: + case status::not_implemented: + case status::bad_gateway: + case status::service_unavailable: + case status::gateway_timeout: + case status::http_version_not_supported: + case status::variant_also_negotiates: + case status::insufficient_storage: + case status::loop_detected: + case status::not_extended: + case status::network_authentication_required: + case status::network_connect_timeout_error: + return static_cast(v); + + default: + break; + } + return status::unknown; +} + +template +string_view +status_to_string(unsigned v) +{ + switch(static_cast(v)) + { + // 1xx + case status::continue_: return "Continue"; + case status::switching_protocols: return "Switching Protocols"; + case status::processing: return "Processing"; + + // 2xx + case status::ok: return "OK"; + case status::created: return "Created"; + case status::accepted: return "Accepted"; + case status::non_authoritative_information: return "Non-Authoritative Information"; + case status::no_content: return "No Content"; + case status::reset_content: return "Reset Content"; + case status::partial_content: return "Partial Content"; + case status::multi_status: return "Multi-Status"; + case status::already_reported: return "Already Reported"; + case status::im_used: return "IM Used"; + + // 3xx + case status::multiple_choices: return "Multiple Choices"; + case status::moved_permanently: return "Moved Permanently"; + case status::found: return "Found"; + case status::see_other: return "See Other"; + case status::not_modified: return "Not Modified"; + case status::use_proxy: return "Use Proxy"; + case status::temporary_redirect: return "Temporary Redirect"; + case status::permanent_redirect: return "Permanent Redirect"; + + // 4xx + case status::bad_request: return "Bad Request"; + case status::unauthorized: return "Unauthorized"; + case status::payment_required: return "Payment Required"; + case status::forbidden: return "Forbidden"; + case status::not_found: return "Not Found"; + case status::method_not_allowed: return "Method Not Allowed"; + case status::not_acceptable: return "Not Acceptable"; + case status::proxy_authentication_required: return "Proxy Authentication Required"; + case status::request_timeout: return "Request Timeout"; + case status::conflict: return "Conflict"; + case status::gone: return "Gone"; + case status::length_required: return "Length Required"; + case status::precondition_failed: return "Precondition Failed"; + case status::payload_too_large: return "Payload Too Large"; + case status::uri_too_long: return "URI Too Long"; + case status::unsupported_media_type: return "Unsupported Media Type"; + case status::range_not_satisfiable: return "Range Not Satisfiable"; + case status::expectation_failed: return "Expectation Failed"; + case status::misdirected_request: return "Misdirected Request"; + case status::unprocessable_entity: return "Unprocessable Entity"; + case status::locked: return "Locked"; + case status::failed_dependency: return "Failed Dependency"; + case status::upgrade_required: return "Upgrade Required"; + case status::precondition_required: return "Precondition Required"; + case status::too_many_requests: return "Too Many Requests"; + case status::request_header_fields_too_large: return "Request Header Fields Too Large"; + case status::connection_closed_without_response: return "Connection Closed Without Response"; + case status::unavailable_for_legal_reasons: return "Unavailable For Legal Reasons"; + case status::client_closed_request: return "Client Closed Request"; + // 5xx + case status::internal_server_error: return "Internal Server Error"; + case status::not_implemented: return "Not Implemented"; + case status::bad_gateway: return "Bad Gateway"; + case status::service_unavailable: return "Service Unavailable"; + case status::gateway_timeout: return "Gateway Timeout"; + case status::http_version_not_supported: return "HTTP Version Not Supported"; + case status::variant_also_negotiates: return "Variant Also Negotiates"; + case status::insufficient_storage: return "Insufficient Storage"; + case status::loop_detected: return "Loop Detected"; + case status::not_extended: return "Not Extended"; + case status::network_authentication_required: return "Network Authentication Required"; + case status::network_connect_timeout_error: return "Network Connect Timeout Error"; + + default: + break; + } + return ""; +} + +template +status_class +to_status_class(unsigned v) +{ + switch(v / 100) + { + case 1: return status_class::informational; + case 2: return status_class::successful; + case 3: return status_class::redirection; + case 4: return status_class::client_error; + case 5: return status_class::server_error; + default: + break; + } + return status_class::unknown; +} + +} // detail + +inline +status +int_to_status(unsigned v) +{ + return detail::int_to_status(v); +} + +inline +status_class +to_status_class(unsigned v) +{ + return detail::to_status_class(v); +} + +inline +status_class +to_status_class(status v) +{ + return to_status_class(static_cast(v)); +} + +inline +string_view +obsolete_reason(status v) +{ + return detail::status_to_string( + static_cast(v)); +} + +inline +std::ostream& +operator<<(std::ostream& os, status v) +{ + return os << obsolete_reason(v); +} + +} // http +} // beast + +#endif diff --git a/include/beast/http/impl/verb.ipp b/include/beast/http/impl/verb.ipp new file mode 100644 index 0000000000..ad0f1b5ca8 --- /dev/null +++ b/include/beast/http/impl/verb.ipp @@ -0,0 +1,333 @@ +// +// 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_HTTP_IMPL_VERB_IPP +#define BEAST_HTTP_IMPL_VERB_IPP + +#include +#include +#include + +namespace beast { +namespace http { + +namespace detail { + +template +inline +string_view +verb_to_string(verb v) +{ + switch(v) + { + case verb::delete_: return "DELETE"; + case verb::get: return "GET"; + case verb::head: return "HEAD"; + case verb::post: return "POST"; + case verb::put: return "PUT"; + case verb::connect: return "CONNECT"; + case verb::options: return "OPTIONS"; + case verb::trace: return "TRACE"; + + case verb::copy: return "COPY"; + case verb::lock: return "LOCK"; + case verb::mkcol: return "MKCOL"; + case verb::move: return "MOVE"; + case verb::propfind: return "PROPFIND"; + case verb::proppatch: return "PROPPATCH"; + case verb::search: return "SEARCH"; + case verb::unlock: return "UNLOCK"; + case verb::bind: return "BIND"; + case verb::rebind: return "REBIND"; + case verb::unbind: return "UNBIND"; + case verb::acl: return "ACL"; + + case verb::report: return "REPORT"; + case verb::mkactivity: return "MKACTIVITY"; + case verb::checkout: return "CHECKOUT"; + case verb::merge: return "MERGE"; + + case verb::msearch: return "M-SEARCH"; + case verb::notify: return "NOTIFY"; + case verb::subscribe: return "SUBSCRIBE"; + case verb::unsubscribe: return "UNSUBSCRIBE"; + + case verb::patch: return "PATCH"; + case verb::purge: return "PURGE"; + + case verb::mkcalendar: return "MKCALENDAR"; + + case verb::link: return "LINK"; + case verb::unlink: return "UNLINK"; + + case verb::unknown: + return ""; + } + + BOOST_THROW_EXCEPTION(std::invalid_argument{"unknown verb"}); +} + +template +verb +string_to_verb(string_view v) +{ +/* + ACL + BIND + CHECKOUT + CONNECT + COPY + DELETE + GET + HEAD + LINK + LOCK + M-SEARCH + MERGE + MKACTIVITY + MKCALENDAR + MKCOL + MOVE + NOTIFY + OPTIONS + PATCH + POST + PROPFIND + PROPPATCH + PURGE + PUT + REBIND + REPORT + SEARCH + SUBSCRIBE + TRACE + UNBIND + UNLINK + UNLOCK + UNSUBSCRIBE +*/ + if(v.size() < 3) + return verb::unknown; + // s must be null terminated + auto const eq = + [](string_view sv, char const* s) + { + auto p = sv.data(); + for(;;) + { + if(*s != *p) + return false; + ++s; + ++p; + if(! *s) + return p == sv.end(); + } + }; + auto c = v[0]; + v.remove_prefix(1); + switch(c) + { + case 'A': + if(v == "CL") + return verb::acl; + break; + + case 'B': + if(v == "IND") + return verb::bind; + break; + + case 'C': + c = v[0]; + v.remove_prefix(1); + switch(c) + { + case 'H': + if(eq(v, "ECKOUT")) + return verb::checkout; + break; + + case 'O': + if(eq(v, "NNECT")) + return verb::connect; + if(eq(v, "PY")) + return verb::copy; + BEAST_FALLTHROUGH; + + default: + break; + } + break; + + case 'D': + if(eq(v, "ELETE")) + return verb::delete_; + break; + + case 'G': + if(eq(v, "ET")) + return verb::get; + break; + + case 'H': + if(eq(v, "EAD")) + return verb::head; + break; + + case 'L': + if(eq(v, "INK")) + return verb::link; + if(eq(v, "OCK")) + return verb::lock; + break; + + case 'M': + c = v[0]; + v.remove_prefix(1); + switch(c) + { + case '-': + if(eq(v, "SEARCH")) + return verb::msearch; + break; + + case 'E': + if(eq(v, "RGE")) + return verb::merge; + break; + + case 'K': + if(eq(v, "ACTIVITY")) + return verb::mkactivity; + if(v[0] == 'C') + { + v.remove_prefix(1); + if(eq(v, "ALENDAR")) + return verb::mkcalendar; + if(eq(v, "OL")) + return verb::mkcol; + break; + } + break; + + case 'O': + if(eq(v, "VE")) + return verb::move; + BEAST_FALLTHROUGH; + + default: + break; + } + break; + + case 'N': + if(eq(v, "OTIFY")) + return verb::notify; + break; + + case 'O': + if(eq(v, "PTIONS")) + return verb::options; + break; + + case 'P': + c = v[0]; + v.remove_prefix(1); + switch(c) + { + case 'A': + if(eq(v, "TCH")) + return verb::patch; + break; + + case 'O': + if(eq(v, "ST")) + return verb::post; + break; + + case 'R': + if(eq(v, "OPFIND")) + return verb::propfind; + if(eq(v, "OPPATCH")) + return verb::proppatch; + break; + + case 'U': + if(eq(v, "RGE")) + return verb::purge; + if(eq(v, "T")) + return verb::put; + BEAST_FALLTHROUGH; + + default: + break; + } + break; + + case 'R': + if(v[0] != 'E') + break; + v.remove_prefix(1); + if(eq(v, "BIND")) + return verb::rebind; + if(eq(v, "PORT")) + return verb::report; + break; + + case 'S': + if(eq(v, "EARCH")) + return verb::search; + if(eq(v, "UBSCRIBE")) + return verb::subscribe; + break; + + case 'T': + if(eq(v, "RACE")) + return verb::trace; + break; + + case 'U': + if(v[0] != 'N') + break; + v.remove_prefix(1); + if(eq(v, "BIND")) + return verb::unbind; + if(eq(v, "LINK")) + return verb::unlink; + if(eq(v, "LOCK")) + return verb::unlock; + if(eq(v, "SUBSCRIBE")) + return verb::unsubscribe; + break; + + default: + break; + } + + return verb::unknown; +} + +} // detail + +inline +string_view +to_string(verb v) +{ + return detail::verb_to_string(v); +} + +inline +verb +string_to_verb(string_view s) +{ + return detail::string_to_verb(s); +} + +} // http +} // beast + +#endif diff --git a/include/beast/http/impl/write.ipp b/include/beast/http/impl/write.ipp index a6e640254d..1060a4f081 100644 --- a/include/beast/http/impl/write.ipp +++ b/include/beast/http/impl/write.ipp @@ -8,582 +8,732 @@ #ifndef BEAST_HTTP_IMPL_WRITE_IPP #define BEAST_HTTP_IMPL_WRITE_IPP -#include -#include -#include +#include #include -#include -#include +#include +#include #include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include -#include -#include +#include +#include #include #include -#include namespace beast { namespace http { - namespace detail { -template -void -write_start_line(DynamicBuffer& dynabuf, - header const& msg) +template +class write_some_op { - BOOST_ASSERT(msg.version == 10 || msg.version == 11); - write(dynabuf, msg.method); - write(dynabuf, " "); - write(dynabuf, msg.url); - switch(msg.version) + Stream& s_; + serializer& sr_; + Handler h_; + + class lambda { - case 10: - write(dynabuf, " HTTP/1.0\r\n"); - break; - case 11: - write(dynabuf, " HTTP/1.1\r\n"); - break; - } -} + write_some_op& op_; -template -void -write_start_line(DynamicBuffer& dynabuf, - header const& msg) -{ - BOOST_ASSERT(msg.version == 10 || msg.version == 11); - switch(msg.version) - { - case 10: - write(dynabuf, "HTTP/1.0 "); - break; - case 11: - write(dynabuf, "HTTP/1.1 "); - break; - } - write(dynabuf, msg.status); - write(dynabuf, " "); - write(dynabuf, msg.reason); - write(dynabuf, "\r\n"); -} + public: + bool invoked = false; -template -void -write_fields(DynamicBuffer& dynabuf, FieldSequence const& fields) -{ - static_assert(is_DynamicBuffer::value, - "DynamicBuffer requirements not met"); - //static_assert(is_FieldSequence::value, - // "FieldSequence requirements not met"); - for(auto const& field : fields) - { - write(dynabuf, field.name()); - write(dynabuf, ": "); - write(dynabuf, field.value()); - write(dynabuf, "\r\n"); - } -} - -} // detail - -//------------------------------------------------------------------------------ - -namespace detail { - -template -class write_streambuf_op -{ - struct data - { - bool cont; - Stream& s; - streambuf sb; - int state = 0; - - data(Handler& handler, Stream& s_, - streambuf&& sb_) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , s(s_) - , sb(std::move(sb_)) + explicit + lambda(write_some_op& op) + : op_(op) { } + + template + void + operator()(error_code& ec, + ConstBufferSequence const& buffers) + { + invoked = true; + ec.assign(0, ec.category()); + return op_.s_.async_write_some( + buffers, std::move(op_)); + } }; - handler_ptr d_; - public: - write_streambuf_op(write_streambuf_op&&) = default; - write_streambuf_op(write_streambuf_op const&) = default; + write_some_op(write_some_op&&) = default; + write_some_op(write_some_op const&) = default; - template - write_streambuf_op(DeducedHandler&& h, Stream& s, - Args&&... args) - : d_(std::forward(h), - s, std::forward(args)...) + template + write_some_op(DeducedHandler&& h, + Stream& s, serializer& sr) + : s_(s) + , sr_(sr) + , h_(std::forward(h)) { - (*this)(error_code{}, 0, false); } + void + operator()(); + void operator()(error_code ec, - std::size_t bytes_transferred, bool again = true); + std::size_t bytes_transferred); friend void* asio_handler_allocate( - std::size_t size, write_streambuf_op* op) + std::size_t size, write_some_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->h_)); } friend void asio_handler_deallocate( - void* p, std::size_t size, write_streambuf_op* op) + void* p, std::size_t size, write_some_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->h_)); } friend - bool asio_handler_is_continuation(write_streambuf_op* op) + bool asio_handler_is_continuation(write_some_op* op) { - return op->d_->cont; + using boost::asio::asio_handler_is_continuation; + return asio_handler_is_continuation( + std::addressof(op->h_)); } template friend - void asio_handler_invoke(Function&& f, write_streambuf_op* op) + void asio_handler_invoke(Function&& f, write_some_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); - } -}; - -template -void -write_streambuf_op:: -operator()(error_code ec, std::size_t, bool again) -{ - auto& d = *d_; - d.cont = d.cont || again; - while(! ec && d.state != 99) - { - switch(d.state) - { - case 0: - { - d.state = 99; - boost::asio::async_write(d.s, - d.sb.data(), std::move(*this)); - return; - } - } - } - d_.invoke(ec); -} - -} // detail - -template -void -write(SyncWriteStream& stream, - header const& msg) -{ - static_assert(is_SyncWriteStream::value, - "SyncWriteStream requirements not met"); - error_code ec; - write(stream, msg, ec); - if(ec) - throw system_error{ec}; -} - -template -void -write(SyncWriteStream& stream, - header const& msg, - error_code& ec) -{ - static_assert(is_SyncWriteStream::value, - "SyncWriteStream requirements not met"); - streambuf sb; - detail::write_start_line(sb, msg); - detail::write_fields(sb, msg.fields); - beast::write(sb, "\r\n"); - boost::asio::write(stream, sb.data(), ec); -} - -template -typename async_completion< - WriteHandler, void(error_code)>::result_type -async_write(AsyncWriteStream& stream, - header const& msg, - WriteHandler&& handler) -{ - static_assert(is_AsyncWriteStream::value, - "AsyncWriteStream requirements not met"); - beast::async_completion completion{handler}; - streambuf sb; - detail::write_start_line(sb, msg); - detail::write_fields(sb, msg.fields); - beast::write(sb, "\r\n"); - detail::write_streambuf_op{ - completion.handler, stream, std::move(sb)}; - return completion.result.get(); -} - -//------------------------------------------------------------------------------ - -namespace detail { - -template -struct write_preparation -{ - message const& msg; - typename Body::writer w; - streambuf sb; - bool chunked; - bool close; - - explicit - write_preparation( - message const& msg_) - : msg(msg_) - , w(msg) - , chunked(token_list{ - msg.fields["Transfer-Encoding"]}.exists("chunked")) - , close(token_list{ - msg.fields["Connection"]}.exists("close") || - (msg.version < 11 && ! msg.fields.exists( - "Content-Length"))) - { - } - - void - init(error_code& ec) - { - w.init(ec); - if(ec) - return; - - write_start_line(sb, msg); - write_fields(sb, msg.fields); - beast::write(sb, "\r\n"); + using boost::asio::asio_handler_invoke; + asio_handler_invoke(f, std::addressof(op->h_)); } }; template + bool isRequest, class Body, + class Fields, class Decorator> +void +write_some_op:: +operator()() +{ + error_code ec; + if(! sr_.is_done()) + { + lambda f{*this}; + sr_.next(ec, f); + if(ec) + { + BOOST_ASSERT(! f.invoked); + return s_.get_io_service().post( + bind_handler(std::move(*this), ec, 0)); + } + if(f.invoked) + { + // *this has been moved from, + // cannot access members here. + return; + } + // What else could it be? + BOOST_ASSERT(sr_.is_done()); + } + return s_.get_io_service().post( + bind_handler(std::move(*this), ec, 0)); +} + +template +void +write_some_op:: +operator()( + error_code ec, std::size_t bytes_transferred) +{ + if(! ec) + { + sr_.consume(bytes_transferred); + if(sr_.is_done()) + if(! sr_.keep_alive()) + ec = error::end_of_stream; + } + h_(ec); +} + +//------------------------------------------------------------------------------ + +struct serializer_is_header_done +{ + template + bool + operator()(serializer& sr) const + { + return sr.is_header_done(); + } +}; + +struct serializer_is_done +{ + template + bool + operator()(serializer& sr) const + { + return sr.is_done(); + } +}; + +//------------------------------------------------------------------------------ + +template< + class Stream, class Handler, class Predicate, + bool isRequest, class Body, + class Fields, class Decorator> class write_op { - struct data - { - bool cont; - Stream& s; - // VFALCO How do we use handler_alloc in write_preparation? - write_preparation< - isRequest, Body, Fields> wp; - int state = 0; - - data(Handler& handler, Stream& s_, - message const& m_) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , s(s_) - , wp(m_) - { - } - }; - - class writef0_lambda - { - write_op& self_; - - public: - explicit - writef0_lambda(write_op& self) - : self_(self) - { - } - - template - void operator()(ConstBufferSequence const& buffers) const - { - auto& d = *self_.d_; - // write header and body - if(d.wp.chunked) - boost::asio::async_write(d.s, - buffer_cat(d.wp.sb.data(), - chunk_encode(false, buffers)), - std::move(self_)); - else - boost::asio::async_write(d.s, - buffer_cat(d.wp.sb.data(), - buffers), std::move(self_)); - } - }; - - class writef_lambda - { - write_op& self_; - - public: - explicit - writef_lambda(write_op& self) - : self_(self) - { - } - - template - void operator()(ConstBufferSequence const& buffers) const - { - auto& d = *self_.d_; - // write body - if(d.wp.chunked) - boost::asio::async_write(d.s, - chunk_encode(false, buffers), - std::move(self_)); - else - boost::asio::async_write(d.s, - buffers, std::move(self_)); - } - }; - - handler_ptr d_; + int state_ = 0; + Stream& s_; + serializer& sr_; + Handler h_; public: write_op(write_op&&) = default; write_op(write_op const&) = default; - template - write_op(DeducedHandler&& h, Stream& s, Args&&... args) - : d_(std::forward(h), - s, std::forward(args)...) + template + write_op(DeducedHandler&& h, Stream& s, + serializer& sr) + : s_(s) + , sr_(sr) + , h_(std::forward(h)) { - (*this)(error_code{}, 0, false); } void - operator()(error_code ec, - std::size_t bytes_transferred, bool again = true); + operator()(error_code ec); friend void* asio_handler_allocate( std::size_t size, write_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->h_)); } friend void asio_handler_deallocate( void* p, std::size_t size, write_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->h_)); } friend bool asio_handler_is_continuation(write_op* op) { - return op->d_->cont; + using boost::asio::asio_handler_is_continuation; + return op->state_ >= 3 || + asio_handler_is_continuation( + std::addressof(op->h_)); } template friend void asio_handler_invoke(Function&& f, write_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->h_)); + } +}; + +template< + class Stream, class Handler, class Predicate, + bool isRequest, class Body, + class Fields, class Decorator> +void +write_op:: +operator()(error_code ec) +{ + if(ec) + goto upcall; + switch(state_) + { + case 0: + { + if(Predicate{}(sr_)) + { + state_ = 1; + return s_.get_io_service().post( + bind_handler(std::move(*this), ec)); + } + state_ = 2; + return beast::http::async_write_some( + s_, sr_, std::move(*this)); + } + + case 1: + goto upcall; + + case 2: + state_ = 3; + BEAST_FALLTHROUGH; + + case 3: + { + if(Predicate{}(sr_)) + goto upcall; + return beast::http::async_write_some( + s_, sr_, std::move(*this)); + } + } +upcall: + h_(ec); +} + +//------------------------------------------------------------------------------ + +template +class write_msg_op +{ + struct data + { + Stream& s; + serializer sr; + + data(Handler&, Stream& s_, message< + isRequest, Body, Fields>& m_) + : s(s_) + , sr(m_, no_chunk_decorator{}) + { + } + }; + + handler_ptr d_; + +public: + write_msg_op(write_msg_op&&) = default; + write_msg_op(write_msg_op const&) = default; + + template + write_msg_op(DeducedHandler&& h, Stream& s, Args&&... args) + : d_(std::forward(h), + s, std::forward(args)...) + { + } + + void + operator()(); + + void + operator()(error_code ec); + + 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 + 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())); } }; template void -write_op:: -operator()(error_code ec, std::size_t, bool again) +write_msg_op< + Stream, Handler, isRequest, Body, Fields>:: +operator()() { auto& d = *d_; - d.cont = d.cont || again; - while(! ec && d.state != 99) - { - switch(d.state) - { - case 0: - { - d.wp.init(ec); - if(ec) - { - // call handler - d.state = 99; - d.s.get_io_service().post(bind_handler( - std::move(*this), ec, 0, false)); - return; - } - d.state = 1; - break; - } + return async_write(d.s, d.sr, std::move(*this)); +} - case 1: - { - auto const result = - d.wp.w.write(ec, - writef0_lambda{*this}); - if(ec) - { - // call handler - d.state = 99; - d.s.get_io_service().post(bind_handler( - std::move(*this), ec, false)); - return; - } - if(result) - d.state = d.wp.chunked ? 4 : 5; - else - d.state = 2; - return; - } - - // sent header and body - case 2: - d.wp.sb.consume(d.wp.sb.size()); - d.state = 3; - break; - - case 3: - { - auto const result = - d.wp.w.write(ec, - writef_lambda{*this}); - if(ec) - { - // call handler - d.state = 99; - break; - } - if(result) - d.state = d.wp.chunked ? 4 : 5; - else - d.state = 2; - return; - } - - case 4: - // VFALCO Unfortunately the current interface to the - // Writer concept prevents us from coalescing the - // final body chunk with the final chunk delimiter. - // - // write final chunk - d.state = 5; - boost::asio::async_write(d.s, - chunk_encode_final(), std::move(*this)); - return; - - case 5: - if(d.wp.close) - { - // VFALCO TODO Decide on an error code - ec = boost::asio::error::eof; - } - d.state = 99; - break; - } - } +template +void +write_msg_op< + Stream, Handler, isRequest, Body, Fields>:: +operator()(error_code ec) +{ d_.invoke(ec); } -template -class writef0_lambda +//------------------------------------------------------------------------------ + +template +class write_some_lambda { - DynamicBuffer const& sb_; - SyncWriteStream& stream_; - bool chunked_; - error_code& ec_; + Stream& stream_; public: - writef0_lambda(SyncWriteStream& stream, - DynamicBuffer const& sb, bool chunked, error_code& ec) - : sb_(sb) - , stream_(stream) - , chunked_(chunked) - , ec_(ec) + bool invoked = false; + std::size_t bytes_transferred = 0; + + explicit + write_some_lambda(Stream& stream) + : stream_(stream) { } template - void operator()(ConstBufferSequence const& buffers) const + void + operator()(error_code& ec, + ConstBufferSequence const& buffers) { - // write header and body - if(chunked_) - boost::asio::write(stream_, buffer_cat( - sb_.data(), chunk_encode(false, buffers)), ec_); - else - boost::asio::write(stream_, buffer_cat( - sb_.data(), buffers), ec_); + invoked = true; + bytes_transferred = + stream_.write_some(buffers, ec); } }; -template -class writef_lambda +template +class write_lambda { - SyncWriteStream& stream_; - bool chunked_; - error_code& ec_; + Stream& stream_; public: - writef_lambda(SyncWriteStream& stream, - bool chunked, error_code& ec) + bool invoked = false; + std::size_t bytes_transferred = 0; + + explicit + write_lambda(Stream& stream) : stream_(stream) - , chunked_(chunked) - , ec_(ec) { } template - void operator()(ConstBufferSequence const& buffers) const + void + operator()(error_code& ec, + ConstBufferSequence const& buffers) { - // write body - if(chunked_) - boost::asio::write(stream_, - chunk_encode(false, buffers), ec_); - else - boost::asio::write(stream_, buffers, ec_); + invoked = true; + bytes_transferred = boost::asio::write( + stream_, buffers, ec); } }; } // detail +//------------------------------------------------------------------------------ + +namespace detail { + +template< + class SyncWriteStream, + bool isRequest, class Body, class Fields, class Decorator> +void +write_some( + SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, + error_code& ec) +{ + if(! sr.is_done()) + { + write_some_lambda f{stream}; + sr.next(ec, f); + if(ec) + return; + if(f.invoked) + sr.consume(f.bytes_transferred); + if(sr.is_done()) + if(! sr.keep_alive()) + ec = error::end_of_stream; + return; + } + if(! sr.keep_alive()) + ec = error::end_of_stream; + else + ec.assign(0, ec.category()); +} + +template +async_return_type +async_write_some(AsyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, + WriteHandler&& handler) +{ + async_completion init{handler}; + detail::write_some_op, + isRequest, Body, Fields, Decorator>{ + init.completion_handler, stream, sr}(); + return init.result.get(); +} + +} // detail + +template +void +write_some(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr) +{ + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + error_code ec; + write_some(stream, sr, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template +void +write_some(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, + error_code& ec) +{ + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + detail::write_some(stream, sr, ec); +} + +template +async_return_type +async_write_some(AsyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, + WriteHandler&& handler) +{ + static_assert(is_async_write_stream< + AsyncWriteStream>::value, + "AsyncWriteStream requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + return detail::async_write_some(stream, sr, + std::forward(handler)); +} + +//------------------------------------------------------------------------------ + +template +void +write_header(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr) +{ + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + error_code ec; + write_header(stream, sr, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template +void +write_header(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, + error_code& ec) +{ + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + sr.split(true); + if(! sr.is_header_done()) + { + detail::write_lambda f{stream}; + do + { + sr.next(ec, f); + if(ec) + return; + BOOST_ASSERT(f.invoked); + sr.consume(f.bytes_transferred); + } + while(! sr.is_header_done()); + } + else + { + ec.assign(0, ec.category()); + } +} + +template +async_return_type +async_write_header(AsyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, + WriteHandler&& handler) +{ + static_assert(is_async_write_stream< + AsyncWriteStream>::value, + "AsyncWriteStream requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + sr.split(true); + async_completion init{handler}; + detail::write_op, + detail::serializer_is_header_done, + isRequest, Body, Fields, Decorator>{ + init.completion_handler, stream, sr}( + error_code{}, 0); + return init.result.get(); +} + +//------------------------------------------------------------------------------ + +template< + class SyncWriteStream, + bool isRequest, class Body, + class Fields, class Decorator> +void +write( + SyncWriteStream& stream, + serializer& sr) +{ + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + error_code ec; + write(stream, sr, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template< + class SyncWriteStream, + bool isRequest, class Body, + class Fields, class Decorator> +void +write( + SyncWriteStream& stream, + serializer& sr, + error_code& ec) +{ + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + sr.split(false); + for(;;) + { + write_some(stream, sr, ec); + if(ec) + return; + if(sr.is_done()) + break; + } +} + +template +async_return_type +async_write(AsyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, + WriteHandler&& handler) +{ + static_assert(is_async_write_stream< + AsyncWriteStream>::value, + "AsyncWriteStream requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + sr.split(false); + async_completion init{handler}; + detail::write_op, + detail::serializer_is_done, + isRequest, Body, Fields, Decorator>{ + init.completion_handler, stream, sr}( + error_code{}); + return init.result.get(); +} + +//------------------------------------------------------------------------------ + template void write(SyncWriteStream& stream, message const& msg) { - static_assert(is_SyncWriteStream::value, + static_assert(is_sync_write_stream::value, "SyncWriteStream requirements not met"); - static_assert(is_Body::value, + static_assert(is_body::value, "Body requirements not met"); - static_assert(has_writer::value, - "Body has no writer"); - static_assert(is_Writer>::value, - "Writer requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); error_code ec; write(stream, msg, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); } template const& msg, error_code& ec) { - static_assert(is_SyncWriteStream::value, + static_assert(is_sync_write_stream::value, "SyncWriteStream requirements not met"); - static_assert(is_Body::value, + static_assert(is_body::value, "Body requirements not met"); - static_assert(has_writer::value, - "Body has no writer"); - static_assert(is_Writer>::value, - "Writer requirements not met"); - detail::write_preparation wp(msg); - wp.init(ec); - if(ec) - return; - auto result = wp.w.write( - ec, detail::writef0_lambda< - SyncWriteStream, decltype(wp.sb)>{ - stream, wp.sb, wp.chunked, ec}); - if(ec) - return; - wp.sb.consume(wp.sb.size()); - if(! result) - { - detail::writef_lambda wf{ - stream, wp.chunked, ec}; - for(;;) - { - result = wp.w.write(ec, wf); - if(ec) - return; - if(result) - break; - } - } - if(wp.chunked) - { - // VFALCO Unfortunately the current interface to the - // Writer concept prevents us from using coalescing the - // final body chunk with the final chunk delimiter. - // - // write final chunk - boost::asio::write(stream, chunk_encode_final(), ec); - if(ec) - return; - } - if(wp.close) - { - // VFALCO TODO Decide on an error code - ec = boost::asio::error::eof; - } + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + serializer sr{msg}; + write(stream, sr, ec); } template -typename async_completion< - WriteHandler, void(error_code)>::result_type +async_return_type< + WriteHandler, void(error_code)> async_write(AsyncWriteStream& stream, - message const& msg, + message& msg, WriteHandler&& handler) { - static_assert(is_AsyncWriteStream::value, + static_assert( + is_async_write_stream::value, "AsyncWriteStream requirements not met"); - static_assert(is_Body::value, + static_assert(is_body::value, "Body requirements not met"); - static_assert(has_writer::value, - "Body has no writer"); - static_assert(is_Writer>::value, - "Writer requirements not met"); - beast::async_completion completion{handler}; - detail::write_op{completion.handler, stream, msg}; - return completion.result.get(); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + async_completion init{handler}; + detail::write_msg_op, isRequest, + Body, Fields>{init.completion_handler, + stream, msg}(); + return init.result.get(); } //------------------------------------------------------------------------------ -template +namespace detail { + +template +class write_ostream_lambda +{ + std::ostream& os_; + Serializer& sr_; + +public: + write_ostream_lambda(std::ostream& os, + Serializer& sr) + : os_(os) + , sr_(sr) + { + } + + template + void + operator()(error_code& ec, + ConstBufferSequence const& buffers) const + { + ec.assign(0, ec.category()); + if(os_.fail()) + return; + std::size_t bytes_transferred = 0; + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + for(auto it = buffers.begin(); + it != buffers.end(); ++it) + { + boost::asio::const_buffer b = *it; + auto const n = buffer_size(b); + os_.write(buffer_cast(b), n); + if(os_.fail()) + return; + bytes_transferred += n; + } + sr_.consume(bytes_transferred); + } +}; + +} // detail + +template std::ostream& operator<<(std::ostream& os, - header const& msg) + header const& h) { - beast::detail::sync_ostream oss{os}; - error_code ec; - write(oss, msg, ec); - if(ec) - throw system_error{ec}; - return os; + typename Fields::reader fr{ + h, h.version, h.method()}; + return os << buffers(fr.get()); +} + +template +std::ostream& +operator<<(std::ostream& os, + header const& h) +{ + typename Fields::reader fr{ + h, h.version, h.result_int()}; + return os << buffers(fr.get()); } template @@ -689,18 +848,27 @@ std::ostream& operator<<(std::ostream& os, message const& msg) { - static_assert(is_Body::value, + static_assert(is_body::value, "Body requirements not met"); - static_assert(has_writer::value, - "Body has no writer"); - static_assert(is_Writer>::value, - "Writer requirements not met"); - beast::detail::sync_ostream oss{os}; + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + serializer sr{msg}; error_code ec; - write(oss, msg, ec); - if(ec && ec != boost::asio::error::eof) - throw system_error{ec}; + detail::write_ostream_lambda f{os, sr}; + do + { + sr.next(ec, f); + if(os.fail()) + break; + if(ec == error::end_of_stream) + ec.assign(0, ec.category()); + if(ec) + { + os.setstate(std::ios::failbit); + break; + } + } + while(! sr.is_done()); return os; } diff --git a/include/beast/http/message.hpp b/include/beast/http/message.hpp index 70cfbf19c1..4c3fd64482 100644 --- a/include/beast/http/message.hpp +++ b/include/beast/http/message.hpp @@ -10,8 +10,15 @@ #include #include +#include +#include +#include +#include #include +#include +#include #include +#include #include #include #include @@ -19,66 +26,57 @@ namespace beast { namespace http { -#if GENERATING_DOCS -/** A container for a HTTP request or response header. +/** A container for an HTTP request or response header. - A header includes the Start Line and Fields. + This container is derived from the `Fields` template type. + To understand all of the members of this class it is necessary + to view the declaration for the `Fields` type. When using + the default fields container, those declarations are in + @ref fields. - Some use-cases: + Newly constructed header objects have version set to + HTTP/1.1. Newly constructed response objects also have + result code set to @ref status::ok. - @li When the message has no body, such as a response to a HEAD request. - - @li When the caller wishes to defer instantiation of the body. - - @li Invoke algorithms which operate on the header only. + A `header` includes the start-line and header-fields. */ -template -struct header +#if BEAST_DOXYGEN +template +struct header : Fields #else -template +template struct header; template -struct header +struct header : Fields #endif { - /// Indicates if the header is a request or response. -#if GENERATING_DOCS - static bool constexpr is_request = isRequest; + static_assert(is_fields::value, + "Fields requirements not met"); + /// Indicates if the header is a request or response. +#if BEAST_DOXYGEN + using is_request = std::integral_constant; #else - static bool constexpr is_request = true; + using is_request = std::true_type; #endif /// The type representing the fields. using fields_type = Fields; - /** The HTTP version. + /** The HTTP-version. This holds both the major and minor version numbers, using these formulas: @code - major = version / 10; - minor = version % 10; + unsigned major = version / 10; + unsigned minor = version % 10; @endcode + + Newly constructed headers will use HTTP/1.1 by default. */ - int version; - - /** The Request Method - - @note This field is present only if `isRequest == true`. - */ - std::string method; - - /** The Request URI - - @note This field is present only if `isRequest == true`. - */ - std::string url; - - /// The HTTP field values. - fields_type fields; + unsigned version = 11; /// Default constructor header() = default; @@ -95,16 +93,85 @@ struct header /// Copy assignment header& operator=(header const&) = default; - /** Construct the header. + /** Return the request-method verb. - All arguments are forwarded to the constructor - of the `fields` member. + If the request-method is not one of the recognized verbs, + @ref verb::unknown is returned. Callers may use @ref method_string + to retrieve the exact text. - @note This constructor participates in overload resolution - if and only if the first parameter is not convertible to - `header`. + @note This function is only available when `isRequest == true`. + + @see @ref method_string */ -#if GENERATING_DOCS + verb + method() const; + + /** Set the request-method. + + This function will set the method for requests to a known verb. + + @param v The request method verb to set. + This may not be @ref verb::unknown. + + @throws std::invalid_argument when `v == verb::unknown`. + + @note This function is only available when `isRequest == true`. + */ + void + method(verb v); + + /** Return the request-method as a string. + + @note This function is only available when `isRequest == true`. + + @see @ref method + */ + string_view + method_string() const; + + /** Set the request-method. + + This function will set the request-method a known verb + if the string matches, otherwise it will store a copy of + the passed string. + + @param s A string representing the request-method. + + @note This function is only available when `isRequest == true`. + */ + void + method_string(string_view s); + + /** Returns the request-target string. + + @note This function is only available when `isRequest == true`. + */ + string_view + target() const; + + /** Set the request-target string. + + @param s A string representing the request-target. + + @note This function is only available when `isRequest == true`. + */ + void + target(string_view s); + + // VFALCO Don't rearrange these declarations or + // ifdefs, or else the documentation will break. + + /** Constructor + + @param args Arguments forwarded to the `Fields` + base class constructor. + + @note This constructor participates in overload + resolution if and only if the first parameter is + not convertible to @ref header, @ref verb, or + @ref status. + */ +#if BEAST_DOXYGEN template explicit header(Args&&... args); @@ -112,34 +179,53 @@ struct header #else template 0) || ! std::is_convertible< - typename std::decay::type, - header>::value>::type> + ! std::is_convertible::type, header>::value && + ! std::is_convertible::type, verb>::value && + ! std::is_convertible::type, header>::value + >::type> explicit - header(Arg1&& arg1, ArgN&&... argn) - : fields(std::forward(arg1), - std::forward(argn)...) + header(Arg1&& arg1, ArgN&&... argn); + +private: + template + friend struct message; + + template + friend + void + swap(header& m1, header& m2); + + template + header( + verb method, + string_view target_, + unsigned version_, + FieldsArgs&&... fields_args) + : Fields(std::forward(fields_args)...) + , version(version_) + , method_(method) { + target(target_); } + + verb method_ = verb::unknown; }; -/** A container for a HTTP request or response header. +/** A container for an HTTP request or response header. - A header includes the Start Line and Fields. - - Some use-cases: - - @li When the message has no body, such as a response to a HEAD request. - - @li When the caller wishes to defer instantiation of the body. - - @li Invoke algorithms which operate on the header only. + A `header` includes the start-line and header-fields. */ template -struct header +struct header : Fields { + static_assert(is_fields::value, + "Fields requirements not met"); + /// Indicates if the header is a request or response. - static bool constexpr is_request = false; + using is_request = std::false_type; /// The type representing the fields. using fields_type = Fields; @@ -149,16 +235,16 @@ struct header This holds both the major and minor version numbers, using these formulas: @code - major = version / 10; - minor = version % 10; + unsigned major = version / 10; + unsigned minor = version % 10; @endcode + + Newly constructed headers will use HTTP/1.1 by default + unless otherwise specified. */ - int version; + unsigned version = 11; - /// The HTTP field values. - fields_type fields; - - /// Default constructor + /// Default constructor. header() = default; /// Move constructor @@ -173,45 +259,143 @@ struct header /// Copy assignment header& operator=(header const&) = default; - /** Construct the header. + /** Constructor - All arguments are forwarded to the constructor - of the `fields` member. + @param args Arguments forwarded to the `Fields` + base class constructor. - @note This constructor participates in overload resolution - if and only if the first parameter is not convertible to - `header`. + @note This constructor participates in overload + resolution if and only if the first parameter is + not convertible to @ref header, @ref verb, or + @ref status. */ template 0) || ! std::is_convertible< - typename std::decay::type, - header>::value>::type> + ! std::is_convertible::type, status>::value && + ! std::is_convertible::type, header>::value + >::type> explicit - header(Arg1&& arg1, ArgN&&... argn) - : fields(std::forward(arg1), - std::forward(argn)...) - { - } + header(Arg1&& arg1, ArgN&&... argn); #endif - /** The Response Status-Code. + /** The response status-code result. - @note This field is present only if `isRequest == false`. + If the actual status code is not a known code, this + function returns @ref status::unknown. Use @ref result_int + to return the raw status code as a number. + + @note This member is only available when `isRequest == false`. */ - int status; + status + result() const; - /** The Response Reason-Phrase. + /** Set the response status-code. - The Reason-Phrase is obsolete as of rfc7230. + @param v The code to set. - @note This field is present only if `isRequest == false`. + @note This member is only available when `isRequest == false`. */ - std::string reason; + void + result(status v); + + /** Set the response status-code as an integer. + + This sets the status code to the exact number passed in. + If the number does not correspond to one of the known + status codes, the function @ref result will return + @ref status::unknown. Use @ref result_int to obtain the + original raw status-code. + + @param v The status-code integer to set. + + @throws std::invalid_argument if `v > 999`. + */ + void + result(unsigned v); + + /** The response status-code expressed as an integer. + + This returns the raw status code as an integer, even + when that code is not in the list of known status codes. + + @note This member is only available when `isRequest == false`. + */ + unsigned + result_int() const; + + /** Return the response reason-phrase. + + The reason-phrase is obsolete as of rfc7230. + + @note This function is only available when `isRequest == false`. + */ + string_view + reason() const; + + /** Set the response reason-phrase (deprecated) + + This function sets a custom reason-phrase to a copy of + the string passed in. Normally it is not necessary to set + the reason phrase on an outgoing response object; the + implementation will automatically use the standard reason + text for the corresponding status code. + + To clear a previously set custom phrase, pass an empty + string. This will restore the default standard reason text + based on the status code used when serializing. + + The reason-phrase is obsolete as of rfc7230. + + @param s The string to use for the reason-phrase. + + @note This function is only available when `isRequest == false`. + */ + void + reason(string_view s); + +private: +#if ! BEAST_DOXYGEN + template + friend struct message; + + template + friend + void + swap(header& m1, header& m2); + + template + header( + status result, + unsigned version_, + FieldsArgs&&... fields_args) + : Fields(std::forward(fields_args)...) + , version(version_) + , result_(result) + { + } + + status result_ = status::ok; +#endif }; +/// A typical HTTP request header +template +using request_header = header; + +/// A typical HTTP response header +template +using response_header = header; + /** A container for a complete HTTP message. + This container is derived from the `Fields` template type. + To understand all of the members of this class it is necessary + to view the declaration for the `Fields` type. When using + the default fields container, those declarations are in + @ref fields. + A message can be a request or response, depending on the `isRequest` template argument value. Requests and responses have different types; functions may be overloaded based on @@ -220,6 +404,10 @@ struct header The `Body` template argument type determines the model used to read or write the content body of the message. + Newly constructed messages objects have version set to + HTTP/1.1. Newly constructed response objects also have + result code set to @ref status::ok. + @tparam isRequest `true` if this represents a request, or `false` if this represents a response. Some class data members are conditionally present depending on this value. @@ -229,11 +417,11 @@ struct header @tparam Fields The type of container used to hold the field value pairs. */ -template +template struct message : header { /// The base class used to hold the header portion of the message. - using base_type = header; + using header_type = header; /** The type providing the body traits. @@ -244,151 +432,384 @@ struct message : header /// A value representing the body. typename Body::value_type body; - /// Default constructor + /// Constructor message() = default; - /// Move constructor + /// Constructor message(message&&) = default; - /// Copy constructor + /// Constructor message(message const&) = default; - /// Move assignment + /// Assignment message& operator=(message&&) = default; - /// Copy assignment + /// Assignment message& operator=(message const&) = default; - /** Construct a message from a header. + /** Constructor - Additional arguments, if any, are forwarded to - the constructor of the body member. + @param h The header to move construct from. + + @param body_args Optional arguments forwarded + to the `body` constructor. */ - template + template explicit - message(base_type&& base, Args&&... args) - : base_type(std::move(base)) - , body(std::forward(args)...) - { - } + message(header_type&& h, BodyArgs&&... body_args); - /** Construct a message from a header. + /** Constructor. - Additional arguments, if any, are forwarded to - the constructor of the body member. + @param h The header to copy construct from. + + @param body_args Optional arguments forwarded + to the `body` constructor. */ - template + template explicit - message(base_type const& base, Args&&... args) - : base_type(base) - , body(std::forward(args)...) - { - } + message(header_type const& h, BodyArgs&&... body_args); - /** Construct a message. + /** Constructor - @param u An argument forwarded to the body constructor. + @param method The request-method to use - @note This constructor participates in overload resolution - only if `u` is not convertible to `base_type`. + @param target The request-target. + + @param version The HTTP-version + + @note This function is only available when `isRequest == true`. */ - template::type, base_type>::value>::type +#if BEAST_DOXYGEN + message(verb method, string_view target, unsigned version); +#else + template::value>::type> + message(verb method, string_view target, Version version); #endif - > - explicit - message(U&& u) - : body(std::forward(u)) - { - } - /** Construct a message. + /** Constructor - @param u An argument forwarded to the body constructor. + @param method The request-method to use - @param v An argument forwarded to the fields constructor. + @param target The request-target. - @note This constructor participates in overload resolution - only if `u` is not convertible to `base_type`. + @param version The HTTP-version + + @param body_arg An argument forwarded to the `body` constructor. + + @note This function is only available when `isRequest == true`. */ - template::type, base_type>::value>::type +#if BEAST_DOXYGEN + template + message(verb method, string_view target, + unsigned version, BodyArg&& body_arg); +#else + template::value>::type> + message(verb method, string_view target, + Version version, BodyArg&& body_arg); #endif - > - message(U&& u, V&& v) - : base_type(std::forward(v)) - , body(std::forward(u)) - { - } + + /** Constructor + + @param method The request-method to use + + @param target The request-target. + + @param version The HTTP-version + + @param body_arg An argument forwarded to the `body` constructor. + + @param fields_arg An argument forwarded to the `Fields` constructor. + + @note This function is only available when `isRequest == true`. + */ +#if BEAST_DOXYGEN + template + message(verb method, string_view target, unsigned version, + BodyArg&& body_arg, FieldsArg&& fields_arg); +#else + template::value>::type> + message(verb method, string_view target, Version version, + BodyArg&& body_arg, FieldsArg&& fields_arg); +#endif + + /** Constructor + + @param result The status-code for the response + + @param version The HTTP-version + + @note This member is only available when `isRequest == false`. + */ +#if BEAST_DOXYGEN + message(status result, unsigned version); +#else + template::value>::type> + message(status result, Version version); +#endif + + /** Constructor + + @param result The status-code for the response + + @param version The HTTP-version + + @param body_arg An argument forwarded to the `body` constructor. + + @note This member is only available when `isRequest == false`. + */ +#if BEAST_DOXYGEN + template + message(status result, unsigned version, BodyArg&& body_arg); +#else + template::value>::type> + message(status result, Version version, BodyArg&& body_arg); +#endif + + /** Constructor + + @param result The status-code for the response + + @param version The HTTP-version + + @param body_arg An argument forwarded to the `body` constructor. + + @param fields_arg An argument forwarded to the `Fields` base class constructor. + + @note This member is only available when `isRequest == false`. + */ +#if BEAST_DOXYGEN + template + message(status result, unsigned version, + BodyArg&& body_arg, FieldsArg&& fields_arg); +#else + template::value>::type> + message(status result, Version version, + BodyArg&& body_arg, FieldsArg&& fields_arg); +#endif + + /** Constructor + + The header and body are default-constructed. + */ + explicit + message(std::piecewise_construct_t); /** Construct a message. - @param un A tuple forwarded as a parameter pack to the body constructor. + @param body_args A tuple forwarded as a parameter + pack to the body constructor. */ - template - message(std::piecewise_construct_t, std::tuple un) - : message(std::piecewise_construct, un, - beast::detail::make_index_sequence{}) - { - } - - /** Construct a message. - - @param un A tuple forwarded as a parameter pack to the body constructor. - - @param vn A tuple forwarded as a parameter pack to the fields constructor. - */ - template + template message(std::piecewise_construct_t, - std::tuple&& un, std::tuple&& vn) - : message(std::piecewise_construct, un, vn, - beast::detail::make_index_sequence{}, - beast::detail::make_index_sequence{}) - { - } + std::tuple body_args); + + /** Construct a message. + + @param body_args A tuple forwarded as a parameter + pack to the body constructor. + + @param fields_args A tuple forwarded as a parameter + pack to the `Fields` constructor. + */ + template + message(std::piecewise_construct_t, + std::tuple body_args, + std::tuple fields_args); /// Returns the header portion of the message - base_type& - base() - { - return *this; - } - - /// Returns the header portion of the message - base_type const& + header_type const& base() const { return *this; } -private: - template - message(std::piecewise_construct_t, - std::tuple& tu, beast::detail::index_sequence) - : body(std::forward(std::get(tu))...) + /// Returns the header portion of the message + header_type& + base() { + return *this; } - template - message(std::piecewise_construct_t, - std::tuple& tu, std::tuple& tv, - beast::detail::index_sequence, - beast::detail::index_sequence) - : base_type(std::forward(std::get(tv))...) - , body(std::forward(std::get(tu))...) + /// Returns `true` if the chunked Transfer-Encoding is specified + bool + chunked() const { + return this->get_chunked_impl(); } + + /** Set or clear the chunked Transfer-Encoding + + This function will set or removed the "chunked" transfer + encoding as the last item in the list of encodings in the + field. + + If the result of removing the chunked token results in an + empty string, the field is erased. + + The Content-Length field is erased unconditionally. + */ + void + chunked(bool value); + + /** Set or clear the Content-Length field + + This function adjusts the Content-Length field as follows: + + @li If `value` specifies a value, the Content-Length field + is set to the value. Otherwise + + @li The Content-Length field is erased. + + If "chunked" token appears as the last item in the + Transfer-Encoding field it is unconditionally removed. + + @param value The value to set for Content-Length. + */ + void + content_length(boost::optional const& value); + + /** Returns `true` if the message semantics indicate keep-alive + + The value depends on the version in the message, which must + be set to the final value before this function is called or + else the return value is unreliable. + */ + bool + keep_alive() const + { + return this->get_keep_alive_impl(this->version); + } + + /** Set the keep-alive message semantic option + + This function adjusts the Connection field to indicate + whether or not the connection should be kept open after + the corresponding response. The result depends on the + version set on the message, which must be set to the + final value before making this call. + + @param value `true` if the connection should persist. + */ + void + keep_alive(bool value) + { + this->set_keep_alive_impl(this->version, value); + } + + /** Returns the payload size of the body in octets if possible. + + This function invokes the @b Body algorithm to measure + the number of octets in the serialized body container. If + there is no body, this will return zero. Otherwise, if the + body exists but is not known ahead of time, `boost::none` + is returned (usually indicating that a chunked Transfer-Encoding + will be used). + + @note The value of the Content-Length field in the message + is not inspected. + */ + boost::optional + payload_size() const; + + /** Prepare the message payload fields for the body. + + This function will adjust the Content-Length and + Transfer-Encoding field values based on the properties + of the body. + + @par Example + @code + request req{verb::post, "/"}; + req.set(field::user_agent, "Beast"); + req.body = "Hello, world!"; + req.prepare_payload(); + @endcode + */ + void + prepare_payload() + { + prepare_payload(typename header_type::is_request{}); + } + +private: + static_assert(is_body::value, + "Body requirements not met"); + + template< + class... BodyArgs, + std::size_t... IBodyArgs> + message( + std::piecewise_construct_t, + std::tuple& body_args, + beast::detail::index_sequence) + : body(std::forward( + std::get(body_args))...) + { + boost::ignore_unused(body_args); + } + + template< + class... BodyArgs, + class... FieldsArgs, + std::size_t... IBodyArgs, + std::size_t... IFieldsArgs> + message( + std::piecewise_construct_t, + std::tuple& body_args, + std::tuple& fields_args, + beast::detail::index_sequence, + beast::detail::index_sequence) + : header_type(std::forward( + std::get(fields_args))...) + , body(std::forward( + std::get(body_args))...) + { + boost::ignore_unused(body_args); + boost::ignore_unused(fields_args); + } + + boost::optional + payload_size(std::true_type) const + { + return Body::size(body); + } + + boost::optional + payload_size(std::false_type) const + { + return boost::none; + } + + void + prepare_payload(std::true_type); + + void + prepare_payload(std::false_type); }; +/// A typical HTTP request +template +using request = message; + +/// A typical HTTP response +template +using response = message; + //------------------------------------------------------------------------------ -#if GENERATING_DOCS +#if BEAST_DOXYGEN /** Swap two header objects. @par Requirements @@ -412,71 +833,6 @@ swap( message& m1, message& m2); -/// A typical HTTP request header -using request_header = header; - -/// Typical HTTP response header -using response_header = header; - -/// A typical HTTP request -template -using request = message; - -/// A typical HTTP response -template -using response = message; - -//------------------------------------------------------------------------------ - -/** Returns `true` if the HTTP/1 message indicates a keep alive. - - Undefined behavior if version is greater than 11. -*/ -template -bool -is_keep_alive(header const& msg); - -/** Returns `true` if the HTTP/1 message indicates an Upgrade request or response. - - Undefined behavior if version is greater than 11. -*/ -template -bool -is_upgrade(header const& msg); - -/** HTTP/1 connection prepare options. - - @note These values are used with @ref prepare. -*/ -enum class connection -{ - /// Specify Connection: close. - close, - - /// Specify Connection: keep-alive where possible. - keep_alive, - - /// Specify Connection: upgrade. - upgrade -}; - -/** Prepare a HTTP message. - - This function will adjust the Content-Length, Transfer-Encoding, - and Connection fields of the message based on the properties of - the body and the options passed in. - - @param msg The message to prepare. The fields may be modified. - - @param options A list of prepare options. -*/ -template< - bool isRequest, class Body, class Fields, - class... Options> -void -prepare(message& msg, - Options&&... options); - } // http } // beast diff --git a/include/beast/http/parse.hpp b/include/beast/http/parse.hpp deleted file mode 100644 index bcc8b0f521..0000000000 --- a/include/beast/http/parse.hpp +++ /dev/null @@ -1,155 +0,0 @@ -// -// 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_HTTP_PARSE_HPP -#define BEAST_HTTP_PARSE_HPP - -#include -#include -#include - -namespace beast { -namespace http { - -/** Parse an object from a stream. - - This function synchronously reads from a stream and passes - data to the specified parser. The call will block until one - of the following conditions are met: - - @li The parser indicates that parsing is complete. - - @li An error occurs in the stream or parser. - - This function is implemented in terms of one or more calls - to the stream's `read_some` function. The implementation may - read additional octets that lie past the end of the object - being parsed. This additional data is stored in the stream - buffer, which may be used in subsequent calls. - - @note This algorithm is generic, and not specific to HTTP - messages. It is up to the parser to determine what predicate - defines a complete operation. - - @param stream The stream from which the data is to be read. - The type must support the @b SyncReadStream concept. - - @param dynabuf A @b DynamicBuffer holding additional bytes - read by the implementation from the stream. This is both - an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser - first. - - @param parser An object meeting the requirements of @b Parser - which will receive the data. - - @throws system_error Thrown on failure. -*/ -template -void -parse(SyncReadStream& stream, - DynamicBuffer& dynabuf, Parser& parser); - -/** Parse an object from a stream. - - This function synchronously reads from a stream and passes - data to the specified parser. The call will block until one - of the following conditions are met: - - @li The parser indicates that parsing is complete. - - @li An error occurs in the stream or parser. - - This function is implemented in terms of one or more calls - to the stream's `read_some` function. The implementation may - read additional octets that lie past the end of the object - being parsed. This additional data is stored in the stream - buffer, which may be used in subsequent calls. - - @note This algorithm is generic, and not specific to HTTP - messages. It is up to the parser to determine what predicate - defines a complete operation. - - @param stream The stream from which the data is to be read. - The type must support the @b SyncReadStream concept. - - @param dynabuf A @b DynamicBuffer holding additional bytes - read by the implementation from the stream. This is both - an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser - first. - - @param parser An object meeting the requirements of @b Parser - which will receive the data. - - @param ec Set to the error, if any occurred. -*/ -template -void -parse(SyncReadStream& stream, - DynamicBuffer& dynabuf, Parser& parser, error_code& ec); - -/** Start an asynchronous operation to parse an object from a stream. - - This function is used to asynchronously read from a stream and - pass the data to the specified parser. The function call always - returns immediately. The asynchronous operation will continue - until one of the following conditions is true: - - @li The parser indicates that parsing is complete. - - @li An error occurs in the stream or parser. - - This operation is implemented in terms of one or more calls to - the next layer's `async_read_some` function, and is known as a - composed operation. 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 object being parsed. This additional data is stored - in the stream buffer, which may be used in subsequent calls. - - @param stream The stream from which the data is to be read. - The type must support the @b AsyncReadStream concept. - - @param dynabuf A @b DynamicBuffer holding additional bytes - read by the implementation from the stream. This is both - an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser - first. - - @param parser An object meeting the requirements of @b Parser - which will receive the data. This object must remain valid - until the completion handler is invoked. - - @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 // 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 -#if GENERATING_DOCS -void_or_deduced -#else -typename async_completion< - ReadHandler, void(error_code)>::result_type -#endif -async_parse(AsyncReadStream& stream, DynamicBuffer& dynabuf, - Parser& parser, ReadHandler&& handler); - -} // http -} // beast - -#include - -#endif diff --git a/include/beast/http/parse_error.hpp b/include/beast/http/parse_error.hpp deleted file mode 100644 index 7b0dc08c0a..0000000000 --- a/include/beast/http/parse_error.hpp +++ /dev/null @@ -1,48 +0,0 @@ -// -// 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_HTTP_PARSE_ERROR_HPP -#define BEAST_HTTP_PARSE_ERROR_HPP - -#include -#include - -namespace beast { -namespace http { - -enum class parse_error -{ - connection_closed = 1, - - bad_method, - bad_uri, - bad_version, - bad_crlf, - - bad_status, - bad_reason, - - bad_field, - bad_value, - bad_content_length, - illegal_content_length, - - invalid_chunk_size, - invalid_ext_name, - invalid_ext_val, - - header_too_big, - body_too_big, - short_read -}; - -} // http -} // beast - -#include - -#endif diff --git a/include/beast/http/parser.hpp b/include/beast/http/parser.hpp new file mode 100644 index 0000000000..108223ef3c --- /dev/null +++ b/include/beast/http/parser.hpp @@ -0,0 +1,322 @@ +// +// 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_HTTP_PARSER_HPP +#define BEAST_HTTP_PARSER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** An HTTP/1 parser for producing a message. + + This class uses the basic HTTP/1 wire format parser to convert + a series of octets into a @ref message using the @ref basic_fields + container to represent the fields. + + @tparam isRequest Indicates whether a request or response + will be parsed. + + @tparam Body The type used to represent the body. This must + meet the requirements of @b Body. + + @tparam Allocator The type of allocator used with the + @ref basic_fields container. + + @note A new instance of the parser is required for each message. +*/ +template< + bool isRequest, + class Body, + class Allocator = std::allocator> +class parser + : public basic_parser> +{ + static_assert(is_body::value, + "Body requirements not met"); + + static_assert(is_body_writer::value, + "BodyWriter requirements not met"); + + template + friend class parser; + + using base_type = basic_parser>; + + message> m_; + typename Body::writer wr_; + std::function cb_; + bool wr_inited_ = false; + +public: + /// The type of message returned by the parser + using value_type = + message>; + + /// Constructor + parser(); + + /// Copy constructor (disallowed) + parser(parser const&) = delete; + + /// Copy assignment (disallowed) + parser& operator=(parser const&) = delete; + + /** Move constructor. + + After the move, the only valid operation + on the moved-from object is destruction. + */ + parser(parser&& other) = default; + + /** Constructor + + @param args Optional arguments forwarded to the + @ref http::header constructor. + + @note This function participates in overload + resolution only if the first argument is not a + @ref parser. + */ +#if BEAST_DOXYGEN + template + explicit + parser(Args&&... args); +#else + template::type>::value>::type> + explicit + parser(Arg1&& arg1, ArgN&&... argn); +#endif + + /** Construct a parser from another parser, changing the Body type. + + This constructs a new parser by move constructing the + header from another parser with a different body type. The + constructed-from parser must not have any parsed body octets or + initialized @b BodyWriter, otherwise an exception is generated. + + @par Example + @code + // Deferred body type commitment + request_parser req0; + ... + request_parser req{std::move(req0)}; + @endcode + + If an exception is thrown, the state of the constructed-from + parser is undefined. + + @param parser The other parser to construct from. After + this call returns, the constructed-from parser may only + be destroyed. + + @param args Optional arguments forwarded to the message + constructor. + + @throws std::invalid_argument Thrown when the constructed-from + parser has already initialized a body writer. + + @note This function participates in overload resolution only + if the other parser uses a different body type. + */ +#if BEAST_DOXYGEN + template +#else + template::value>::type> +#endif + explicit + parser(parser&& parser, Args&&... args); + + /** Returns the parsed message. + + Depending on the parser's progress, + parts of this object may be incomplete. + */ + value_type const& + get() const + { + return m_; + } + + /** Returns the parsed message. + + Depending on the parser's progress, + parts of this object may be incomplete. + */ + value_type& + get() + { + return m_; + } + + /** Returns ownership of the parsed message. + + Ownership is transferred to the caller. + Depending on the parser's progress, + parts of this object may be incomplete. + + @par Requires + + @ref value_type is @b MoveConstructible + */ + value_type + release() + { + static_assert(std::is_move_constructible::value, + "MoveConstructible requirements not met"); + return std::move(m_); + } + + /** Set the on_header callback. + + When the callback is set, it is called after the parser + receives a complete header. The function must be invocable with + this signature: + @code + void callback( + parser& p, // `*this` + error_code& ec) // Set to the error, if any + @endcode + The callback will ensure that `!ec` is `true` if there was + no error or set to the appropriate error code if there was one. + + The callback may not call @ref put or @ref put_eof, or + else the behavior is undefined. + */ + void + on_header(std::function cb) + { + cb_ = std::move(cb); + } + +private: + friend class basic_parser; + + void + on_request(verb method, string_view method_str, + string_view target, int version, error_code& ec) + { + try + { + m_.target(target); + if(method != verb::unknown) + m_.method(method); + else + m_.method_string(method_str); + ec.assign(0, ec.category()); + } + catch(std::bad_alloc const&) + { + ec = error::bad_alloc; + } + m_.version = version; + } + + void + on_response(int code, + string_view reason, + int version, error_code& ec) + { + m_.result(code); + m_.version = version; + try + { + m_.reason(reason); + ec.assign(0, ec.category()); + } + catch(std::bad_alloc const&) + { + ec = error::bad_alloc; + } + } + + void + on_field(field name, string_view name_string, + string_view value, error_code& ec) + { + try + { + m_.insert(name, name_string, value); + ec.assign(0, ec.category()); + } + catch(std::bad_alloc const&) + { + ec = error::bad_alloc; + } + } + + void + on_header(error_code& ec) + { + if(cb_) + cb_(*this, ec); + else + ec.assign(0, ec.category()); + } + + void + on_body(boost::optional< + std::uint64_t> const& content_length, + error_code& ec) + { + wr_.init(content_length, ec); + wr_inited_ = true; + } + + std::size_t + on_data(string_view s, error_code& ec) + { + return wr_.put(boost::asio::buffer( + s.data(), s.size()), ec); + } + + void + on_chunk(std::uint64_t, + string_view, error_code& ec) + { + ec.assign(0, ec.category()); + } + + void + on_complete(error_code& ec) + { + wr_.finish(ec); + } +}; + +/// An HTTP/1 parser for producing a request message. +template> +using request_parser = parser; + +/// An HTTP/1 parser for producing a response message. +template> +using response_parser = parser; + +} // http +} // beast + +#include + +#endif diff --git a/include/beast/http/parser_v1.hpp b/include/beast/http/parser_v1.hpp deleted file mode 100644 index be1aed1e2d..0000000000 --- a/include/beast/http/parser_v1.hpp +++ /dev/null @@ -1,339 +0,0 @@ -// -// 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_HTTP_PARSER_V1_HPP -#define BEAST_HTTP_PARSER_V1_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -/** Skip body option. - - The options controls whether or not the parser expects to see a - HTTP body, regardless of the presence or absence of certain fields - such as Content-Length. - - Depending on the request, some responses do not carry a body. - For example, a 200 response to a CONNECT request from a tunneling - proxy. In these cases, callers use the @ref skip_body option to - inform the parser that no body is expected. The parser will consider - the message complete after the header has been received. - - Example: - @code - parser_v1 p; - p.set_option(skip_body{true}); - @endcode - - @note Objects of this type are passed to @ref parser_v1::set_option. -*/ -struct skip_body -{ - bool value; - - explicit - skip_body(bool v) - : value(v) - { - } -}; - -/** A parser for producing HTTP/1 messages. - - This class uses the basic HTTP/1 wire format parser to convert - a series of octets into a `message`. - - @note A new instance of the parser is required for each message. -*/ -template -class parser_v1 - : public basic_parser_v1> - , private std::conditional::type -{ -public: - /// The type of message this parser produces. - using message_type = - message; - -private: - using reader = - typename message_type::body_type::reader; - - static_assert(is_Body::value, - "Body requirements not met"); - static_assert(has_reader::value, - "Body has no reader"); - static_assert(is_Reader::value, - "Reader requirements not met"); - - std::string field_; - std::string value_; - message_type m_; - boost::optional r_; - std::uint8_t skip_body_ = 0; - bool flush_ = false; - -public: - /// Default constructor - parser_v1() = default; - - /// Move constructor - parser_v1(parser_v1&&) = default; - - /// Copy constructor (disallowed) - parser_v1(parser_v1 const&) = delete; - - /// Move assignment (disallowed) - parser_v1& operator=(parser_v1&&) = delete; - - /// Copy assignment (disallowed) - parser_v1& operator=(parser_v1 const&) = delete; - - /** Construct the parser. - - @param args Forwarded to the message constructor. - - @note This function participates in overload resolution only - if the first argument is not a parser or fields parser. - */ -#if GENERATING_DOCS - template - explicit - parser_v1(Args&&... args); -#else - template::type, - header_parser_v1>::value && - ! std::is_same::type, parser_v1>::value - >::type> - explicit - parser_v1(Arg1&& arg1, ArgN&&... argn) - : m_(std::forward(arg1), - std::forward(argn)...) - { - } -#endif - - /** Construct the parser from a fields parser. - - @param parser The fields parser to construct from. - @param args Forwarded to the message body constructor. - */ - template - explicit - parser_v1(header_parser_v1& parser, - Args&&... args) - : m_(parser.release(), std::forward(args)...) - { - static_cast>&>(*this) = parser; - } - - /// Set the skip body option. - void - set_option(skip_body const& o) - { - skip_body_ = o.value ? 1 : 0; - } - - /** Returns the parsed message. - - Only valid if @ref complete would return `true`. - */ - message_type const& - get() const - { - return m_; - } - - /** Returns the parsed message. - - Only valid if @ref complete would return `true`. - */ - message_type& - get() - { - return m_; - } - - /** Returns ownership of the parsed message. - - Ownership is transferred to the caller. Only - valid if @ref complete would return `true`. - - Requires: - `message` is @b MoveConstructible - */ - message_type - release() - { - static_assert(std::is_move_constructible::value, - "MoveConstructible requirements not met"); - return std::move(m_); - } - -private: - friend class basic_parser_v1; - - void flush() - { - if(! flush_) - return; - flush_ = false; - BOOST_ASSERT(! field_.empty()); - m_.fields.insert(field_, value_); - field_.clear(); - value_.clear(); - } - - void on_start(error_code&) - { - } - - void on_method(boost::string_ref const& s, error_code&) - { - this->method_.append(s.data(), s.size()); - } - - void on_uri(boost::string_ref const& s, error_code&) - { - this->uri_.append(s.data(), s.size()); - } - - void on_reason(boost::string_ref const& s, error_code&) - { - this->reason_.append(s.data(), s.size()); - } - - void on_request_or_response(std::true_type) - { - m_.method = std::move(this->method_); - m_.url = std::move(this->uri_); - } - - void on_request_or_response(std::false_type) - { - m_.status = this->status_code(); - m_.reason = std::move(this->reason_); - } - - void on_request(error_code&) - { - on_request_or_response( - std::integral_constant{}); - } - - void on_response(error_code&) - { - on_request_or_response( - std::integral_constant{}); - } - - void on_field(boost::string_ref const& s, error_code&) - { - flush(); - field_.append(s.data(), s.size()); - } - - void on_value(boost::string_ref const& s, error_code&) - { - value_.append(s.data(), s.size()); - flush_ = true; - } - - void - on_header(std::uint64_t, error_code&) - { - flush(); - m_.version = 10 * this->http_major() + this->http_minor(); - } - - body_what - on_body_what(std::uint64_t, error_code& ec) - { - if(skip_body_) - return body_what::skip; - r_.emplace(m_); - r_->init(ec); - return body_what::normal; - } - - void on_body(boost::string_ref const& s, error_code& ec) - { - r_->write(s.data(), s.size(), ec); - } - - void on_complete(error_code&) - { - } -}; - -/** Create a new parser from a fields parser. - - Associates a Body type with a fields parser, and returns - a new parser which parses a complete message object - containing the original message fields and a new body - of the specified body type. - - This function allows HTTP messages to be parsed in two stages. - First, the fields are parsed and control is returned. Then, - the caller can choose at run-time, the type of Body to - associate with the message. And finally, complete the parse - in a second call. - - @param parser The fields parser to construct from. Ownership - of the message fields in the fields parser is transferred - as if by call to @ref header_parser_v1::release. - - @param args Forwarded to the body constructor of the message - in the new parser. - - @return A parser for a message with the specified @b Body type. - - @par Example - @code - headers_parser ph; - ... - auto p = with_body(ph); - ... - message m = p.release(); - @endcode -*/ -template -parser_v1 -with_body(header_parser_v1& parser, - Args&&... args) -{ - return parser_v1( - parser, std::forward(args)...); -} - -} // http -} // beast - -#endif diff --git a/include/beast/http/read.hpp b/include/beast/http/read.hpp index 54dd57963e..72fea92ff7 100644 --- a/include/beast/http/read.hpp +++ b/include/beast/http/read.hpp @@ -9,267 +9,717 @@ #define BEAST_HTTP_READ_HPP #include -#include +#include #include +#include #include namespace beast { namespace http { -/** Read a HTTP/1 header from a stream. +/** Read part of a message from a stream using a parser. - This function is used to synchronously read a header - from a stream. The call blocks until one of the following - conditions is true: + This function is used to read part of a message from a stream into a + subclass of @ref basic_parser. + The call will block until one of the following conditions is true: - @li An entire header is read in. + @li A call to @ref basic_parser::put with a non-empty buffer sequence + is successful. - @li An error occurs in the stream or parser. + @li An error occurs. - This function is implemented in terms of one or more calls - to the stream's `read_some` function. The implementation may - read additional octets that lie past the end of the message - fields being parsed. This additional data is stored in the - stream buffer, which may be used in subsequent calls. + This operation is implemented in terms of one or + more calls to the stream's `read_some` function. + The implementation may read additional octets that lie past the + end of the message being read. This additional data is stored + in the dynamic buffer, which must be retained for subsequent reads. - If the message corresponding to the header being received - contains a message body, it is the callers responsibility - to cause the body to be read in before attempting to read - the next message. + If the stream returns the error `boost::asio::error::eof` indicating the + end of file during a read, the error returned from this function will be: + + @li @ref error::end_of_stream if no octets were parsed, or + + @li @ref error::partial_message if any octets were parsed but the + message was incomplete, otherwise: + + @li A successful result. A subsequent attempt to read will + return @ref error::end_of_stream @param stream The stream from which the data is to be read. - The type must support the @b `SyncReadStream` concept. + The type must support the @b SyncReadStream concept. - @param dynabuf A @b `DynamicBuffer` holding additional bytes + @param buffer A @b DynamicBuffer holding additional bytes read by the implementation from the stream. This is both an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser + dynamic buffer's input sequence will be given to the parser first. - @param msg An object used to store the header. Any contents - will be overwritten. The type must support copy assignment - or move assignment. + @param parser The parser to use. @throws system_error Thrown on failure. */ -template +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - header& msg); +read_some( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser); -/** Read a HTTP/1 header from a stream. +/** Read part of a message from a stream using a parser. - This function is used to synchronously read a header - from a stream. The call blocks until one of the following - conditions is true: + This function is used to read part of a message from a stream into a + subclass of @ref basic_parser. + The call will block until one of the following conditions is true: - @li An entire header is read in. + @li A call to @ref basic_parser::put with a non-empty buffer sequence + is successful. - @li An error occurs in the stream or parser. + @li An error occurs. - This function is implemented in terms of one or more calls - to the stream's `read_some` function. The implementation may - read additional octets that lie past the end of the message - fields being parsed. This additional data is stored in the - stream buffer, which may be used in subsequent calls. + This operation is implemented in terms of one or + more calls to the stream's `read_some` function. + The implementation may read additional octets that lie past the + end of the message being read. This additional data is stored + in the dynamic buffer, which must be retained for subsequent reads. - If the message corresponding to the header being received - contains a message body, it is the callers responsibility - to cause the body to be read in before attempting to read - the next message. + If the stream returns the error `boost::asio::error::eof` indicating the + end of file during a read, the error returned from this function will be: + + @li @ref error::end_of_stream if no octets were parsed, or + + @li @ref error::partial_message if any octets were parsed but the + message was incomplete, otherwise: + + @li A successful result. A subsequent attempt to read will + return @ref error::end_of_stream + + The function returns the number of bytes processed from the dynamic + buffer. The caller should remove these bytes by calling `consume` on + the dynamic buffer, regardless of any error. @param stream The stream from which the data is to be read. - The type must support the @b `SyncReadStream` concept. + The type must support the @b SyncReadStream concept. - @param dynabuf A @b `DynamicBuffer` holding additional bytes + @param buffer A @b DynamicBuffer holding additional bytes read by the implementation from the stream. This is both an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser + dynamic buffer's input sequence will be given to the parser first. - @param msg An object used to store the header. Any contents - will be overwritten. The type must support copy assignment - or move assignment. + @param parser The parser to use. @param ec Set to the error, if any occurred. */ -template +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - header& msg, - error_code& ec); +read_some( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + error_code& ec); -/** Read a HTTP/1 header asynchronously from a stream. +/** Read part of a message asynchronously from a stream using a parser. - This function is used to asynchronously read a header from - a stream. The function call always returns immediately. The - asynchronous operation will continue until one of the following - conditions is true: + This function is used to asynchronously read part of a message from + a stream into a subclass of @ref basic_parser. + The function call always returns immediately. The asynchronous operation + will continue until one of the following conditions is true: - @li An entire header is read in. + @li A call to @ref basic_parser::put with a non-empty buffer sequence + is successful. - @li An error occurs in the stream or parser. + @li An error occurs. - This operation is implemented in terms of one or more calls to - the stream's `async_read_some` function, and is known as a + 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 composed operation. 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 message fields being parsed. This additional data is - stored in the stream buffer, which may be used in subsequent calls. - - If the message corresponding to the header being received - contains a message body, it is the callers responsibility - to cause the body to be read in before attempting to read - the next message. - - @param stream The stream to read the message from. - The type must support the @b `AsyncReadStream` concept. - - @param dynabuf A @b `DynamicBuffer` holding additional bytes - read by the implementation from the stream. This is both - an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser - first. - - @param msg An object used to store the header. Any contents - will be overwritten. The type must support copy assignment or - move assignment. The object must remain valid at least until - the completion handler is called; ownership is not transferred. - - @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 -#if GENERATING_DOCS -void_or_deduced -#else -typename async_completion< - ReadHandler, void(error_code)>::result_type -#endif -async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf, - header& msg, - ReadHandler&& handler); - -/** Read a HTTP/1 message from a stream. - - This function is used to synchronously read a message from - a stream. The call blocks until one of the following conditions - is true: - - @li A complete message is read in. - - @li An error occurs in the stream or parser. - - This function is implemented in terms of one or more calls - to the stream's `read_some` function. The implementation may - read additional octets that lie past the end of the message - being parsed. This additional data is stored in the stream - buffer, which may be used in subsequent calls. - - @param stream The stream from which the data is to be read. - The type must support the @b `SyncReadStream` concept. - - @param dynabuf A @b `DynamicBuffer` holding additional bytes - read by the implementation from the stream. This is both - an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser - first. - - @param msg An object used to store the message. Any - contents will be overwritten. The type must support - copy assignment or move assignment. - - @throws system_error Thrown on failure. -*/ -template -void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - message& msg); - -/** Read a HTTP/1 message from a stream. - - This function is used to synchronously read a message from - a stream. The call blocks until one of the following conditions - is true: - - @li A complete message is read in. - - @li An error occurs in the stream or parser. - - This function is implemented in terms of one or more calls - to the stream's `read_some` function. The implementation may - read additional octets that lie past the end of the message - being parsed. This additional data is stored in the stream - buffer, which may be used in subsequent calls. - - @param stream The stream from which the data is to be read. - The type must support the @b `SyncReadStream` concept. - - @param dynabuf A @b `DynamicBuffer` holding additional bytes - read by the implementation from the stream. This is both - an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser - first. - - @param msg An object used to store the message. Any - contents will be overwritten. The type must support - copy assignment or move assignment. - - @param ec Set to the error, if any occurred. -*/ -template -void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - message& msg, - error_code& ec); - -/** Read a HTTP/1 message asynchronously from a stream. - - This function is used to asynchronously read a message from - a stream. The function call always returns immediately. The - asynchronous operation will continue until one of the following - conditions is true: - - @li A complete message is read in. - - @li An error occurs in the stream or parser. - - This operation is implemented in terms of one or more calls to - the stream's `async_read_some` function, and is known as a - composed operation. 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 message being parsed. This additional data is stored + end of the object being parsed. This additional data is stored in the stream buffer, which may be used in subsequent calls. - @param stream The stream to read the message from. - The type must support the @b `AsyncReadStream` concept. + If the stream returns the error `boost::asio::error::eof` indicating the + end of file during a read, the error returned from this function will be: - @param dynabuf A @b `DynamicBuffer` holding additional bytes + @li @ref error::end_of_stream if no octets were parsed, or + + @li @ref error::partial_message if any octets were parsed but the + message was incomplete, otherwise: + + @li A successful result. A subsequent attempt to read will + return @ref error::end_of_stream + + @param stream The stream from which the data is to be read. + The type must support the @b AsyncReadStream concept. + + @param buffer A @b DynamicBuffer holding additional bytes read by the implementation from the stream. This is both an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser + dynamic buffer's input sequence will be given to the parser first. - @param msg An object used to store the header. Any contents - will be overwritten. The type must support copy assignment or - move assignment. The object must remain valid at least until - the completion handler is called; ownership is not transferred. + @param parser The parser to use. + The object must remain valid at least until the + handler is called; ownership is not transferred. + + @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 // 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`. + + The completion handler will receive as a parameter the number + of octets processed from the dynamic buffer. The octets should + be removed by calling `consume` on the dynamic buffer after + the read completes, regardless of any error. +*/ +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived, + class ReadHandler> +#if BEAST_DOXYGEN + void_or_deduced +#else +async_return_type< + ReadHandler, void(error_code)> +#endif +async_read_some( + AsyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + ReadHandler&& handler); + +//------------------------------------------------------------------------------ + +/** Read a header from a stream using a parser. + + This function is used to read a header from a stream into a subclass + of @ref basic_parser. + The call will block until one of the following conditions is true: + + @li @ref basic_parser::is_header_done returns `true` + + @li An error occurs. + + This operation is implemented in terms of one or + more calls to the stream's `read_some` function. + The implementation may read additional octets that lie past the + end of the message being read. This additional data is stored + in the dynamic buffer, which must be retained for subsequent reads. + + If the stream returns the error `boost::asio::error::eof` indicating the + end of file during a read, the error returned from this function will be: + + @li @ref error::end_of_stream if no octets were parsed, or + + @li @ref error::partial_message if any octets were parsed but the + message was incomplete, otherwise: + + @li A successful result. A subsequent attempt to read will + return @ref error::end_of_stream + + @param stream The stream from which the data is to be read. + The type must support the @b SyncReadStream concept. + + @param buffer A @b DynamicBuffer holding additional bytes + read by the implementation from the stream. This is both + an input and an output parameter; on entry, any data in the + dynamic buffer's input sequence will be given to the parser + first. + + @param parser The parser to use. + + @throws system_error Thrown on failure. + + @note The implementation will call @ref basic_parser::eager + with the value `false` on the parser passed in. +*/ +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +void +read_header( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser); + +/** Read a header from a stream using a parser. + + This function is used to read a header from a stream into a subclass + of @ref basic_parser. + The call will block until one of the following conditions is true: + + @li @ref basic_parser::is_header_done returns `true` + + @li An error occurs. + + This operation is implemented in terms of one or + more calls to the stream's `read_some` function. + The implementation may read additional octets that lie past the + end of the message being read. This additional data is stored + in the dynamic buffer, which must be retained for subsequent reads. + + If the stream returns the error `boost::asio::error::eof` indicating the + end of file during a read, the error returned from this function will be: + + @li @ref error::end_of_stream if no octets were parsed, or + + @li @ref error::partial_message if any octets were parsed but the + message was incomplete, otherwise: + + @li A successful result. A subsequent attempt to read will + return @ref error::end_of_stream + + @param stream The stream from which the data is to be read. + The type must support the @b SyncReadStream concept. + + @param buffer A @b DynamicBuffer holding additional bytes + read by the implementation from the stream. This is both + an input and an output parameter; on entry, any data in the + dynamic buffer's input sequence will be given to the parser + first. + + @param parser The parser to use. + + @param ec Set to the error, if any occurred. + + @note The implementation will call @ref basic_parser::eager + with the value `false` on the parser passed in. +*/ +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +void +read_header( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + error_code& ec); + +/** Read a header from a stream asynchronously using a parser. + + This function is used to asynchronously read a header from a stream + into a subclass of @ref basic_parser. + The function call always returns immediately. The asynchronous operation + will continue until one of the following conditions is true: + + @li @ref basic_parser::is_header_done returns `true` + + @li An error occurs. + + This operation is implemented in terms of one or more calls to + the stream's `async_read_some` function, and is known as a + composed operation. 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 message being read. This additional data is stored + in the dynamic buffer, which must be retained for subsequent reads. + + If the stream returns the error `boost::asio::error::eof` indicating the + end of file during a read, the error returned from this function will be: + + @li @ref error::end_of_stream if no octets were parsed, or + + @li @ref error::partial_message if any octets were parsed but the + message was incomplete, otherwise: + + @li A successful result. A subsequent attempt to read will + return @ref error::end_of_stream + + @param stream The stream from which the data is to be read. + The type must support the @b AsyncReadStream concept. + + @param buffer A @b DynamicBuffer holding additional bytes + read by the implementation from the stream. This is both + an input and an output parameter; on entry, any data in the + dynamic buffer's input sequence will be given to the parser + first. + + @param parser The parser to use. + The object must remain valid at least until the + handler is called; ownership is not transferred. + + @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 // 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`. + + @note The implementation will call @ref basic_parser::eager + with the value `false` on the parser passed in. +*/ +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived, + class ReadHandler> +#if BEAST_DOXYGEN + void_or_deduced +#else +async_return_type< + ReadHandler, void(error_code)> +#endif +async_read_header( + AsyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + ReadHandler&& handler); + +//------------------------------------------------------------------------------ + +/** Read a complete message from a stream using a parser. + + This function is used to read a complete message from a stream into a + subclass of @ref basic_parser. + The call will block until one of the following conditions is true: + + @li @ref basic_parser::is_done returns `true` + + @li An error occurs. + + This operation is implemented in terms of one or + more calls to the stream's `read_some` function. + The implementation may read additional octets that lie past the + end of the message being read. This additional data is stored + in the dynamic buffer, which must be retained for subsequent reads. + + If the stream returns the error `boost::asio::error::eof` indicating the + end of file during a read, the error returned from this function will be: + + @li @ref error::end_of_stream if no octets were parsed, or + + @li @ref error::partial_message if any octets were parsed but the + message was incomplete, otherwise: + + @li A successful result. A subsequent attempt to read will + return @ref error::end_of_stream + + @param stream The stream from which the data is to be read. + The type must support the @b SyncReadStream concept. + + @param buffer A @b DynamicBuffer holding additional bytes + read by the implementation from the stream. This is both + an input and an output parameter; on entry, any data in the + dynamic buffer's input sequence will be given to the parser + first. + + @param parser The parser to use. + + @throws system_error Thrown on failure. + + @note The implementation will call @ref basic_parser::eager + with the value `true` on the parser passed in. +*/ +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +void +read( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser); + +/** Read a complete message from a stream using a parser. + + This function is used to read a complete message from a stream into a + subclass of @ref basic_parser. + The call will block until one of the following conditions is true: + + @li @ref basic_parser::is_done returns `true` + + @li An error occurs. + + This operation is implemented in terms of one or + more calls to the stream's `read_some` function. + The implementation may read additional octets that lie past the + end of the message being read. This additional data is stored + in the dynamic buffer, which must be retained for subsequent reads. + + If the stream returns the error `boost::asio::error::eof` indicating the + end of file during a read, the error returned from this function will be: + + @li @ref error::end_of_stream if no octets were parsed, or + + @li @ref error::partial_message if any octets were parsed but the + message was incomplete, otherwise: + + @li A successful result. A subsequent attempt to read will + return @ref error::end_of_stream + + @param stream The stream from which the data is to be read. + The type must support the @b SyncReadStream concept. + + @param buffer A @b DynamicBuffer holding additional bytes + read by the implementation from the stream. This is both + an input and an output parameter; on entry, any data in the + dynamic buffer's input sequence will be given to the parser + first. + + @param parser The parser to use. + + @param ec Set to the error, if any occurred. + + @note The implementation will call @ref basic_parser::eager + with the value `true` on the parser passed in. +*/ +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +void +read( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + error_code& ec); + +/** Read a complete message from a stream asynchronously using a parser. + + This function is used to asynchronously read a complete message from a + stream into a subclass of @ref basic_parser. + The function call always returns immediately. The asynchronous operation + will continue until one of the following conditions is true: + + @li @ref basic_parser::is_done returns `true` + + @li An error occurs. + + This operation is implemented in terms of one or more calls to + the stream's `async_read_some` function, and is known as a + composed operation. 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 message being read. This additional data is stored + in the dynamic buffer, which must be retained for subsequent reads. + + If the stream returns the error `boost::asio::error::eof` indicating the + end of file during a read, the error returned from this function will be: + + @li @ref error::end_of_stream if no octets were parsed, or + + @li @ref error::partial_message if any octets were parsed but the + message was incomplete, otherwise: + + @li A successful result. A subsequent attempt to read will + return @ref error::end_of_stream + + @param stream The stream from which the data is to be read. + The type must support the @b AsyncReadStream concept. + + @param buffer A @b DynamicBuffer holding additional bytes + read by the implementation from the stream. This is both + an input and an output parameter; on entry, any data in the + dynamic buffer's input sequence will be given to the parser + first. + + @param parser The parser to use. + The object must remain valid at least until the + handler is called; ownership is not transferred. + + @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 // 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`. + + @note The implementation will call @ref basic_parser::eager + with the value `true` on the parser passed in. +*/ +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived, + class ReadHandler> +#if BEAST_DOXYGEN + void_or_deduced +#else +async_return_type< + ReadHandler, void(error_code)> +#endif +async_read( + AsyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + ReadHandler&& handler); + +//------------------------------------------------------------------------------ + +/** Read a complete message from a stream. + + This function is used to read a complete message from a stream using HTTP/1. + The call will block until one of the following conditions is true: + + @li The entire message is read. + + @li An error occurs. + + This operation is implemented in terms of one or + more calls to the stream's `read_some` function. + The implementation may read additional octets that lie past the + end of the message being read. This additional data is stored + in the dynamic buffer, which must be retained for subsequent reads. + + If the stream returns the error `boost::asio::error::eof` indicating the + end of file during a read, the error returned from this function will be: + + @li @ref error::end_of_stream if no octets were parsed, or + + @li @ref error::partial_message if any octets were parsed but the + message was incomplete, otherwise: + + @li A successful result. A subsequent attempt to read will + return @ref error::end_of_stream + + @param stream The stream from which the data is to be read. + The type must support the @b SyncReadStream concept. + + @param buffer A @b DynamicBuffer holding additional bytes + read by the implementation from the stream. This is both + an input and an output parameter; on entry, any data in the + dynamic buffer's input sequence will be given to the parser + first. + + @param msg An object in which to store the message contents. + This object should not have previous contents, otherwise + the behavior is undefined. + The type must be @b MoveAssignable and @b MoveConstructible. + + @throws system_error Thrown on failure. +*/ +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Body, class Allocator> +void +read( + SyncReadStream& stream, + DynamicBuffer& buffer, + message>& msg); + +/** Read a complete message from a stream. + + This function is used to read a complete message from a stream using HTTP/1. + The call will block until one of the following conditions is true: + + @li The entire message is read. + + @li An error occurs. + + This operation is implemented in terms of one or + more calls to the stream's `read_some` function. + The implementation may read additional octets that lie past the + end of the message being read. This additional data is stored + in the dynamic buffer, which must be retained for subsequent reads. + + If the stream returns the error `boost::asio::error::eof` indicating the + end of file during a read, the error returned from this function will be: + + @li @ref error::end_of_stream if no octets were parsed, or + + @li @ref error::partial_message if any octets were parsed but the + message was incomplete, otherwise: + + @li A successful result. A subsequent attempt to read will + return @ref error::end_of_stream + + @param stream The stream from which the data is to be read. + The type must support the @b SyncReadStream concept. + + @param buffer A @b DynamicBuffer holding additional bytes + read by the implementation from the stream. This is both + an input and an output parameter; on entry, any data in the + dynamic buffer's input sequence will be given to the parser + first. + + @param msg An object in which to store the message contents. + This object should not have previous contents, otherwise + the behavior is undefined. + The type must be @b MoveAssignable and @b MoveConstructible. + + @param ec Set to the error, if any occurred. +*/ +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Body, class Allocator> +void +read( + SyncReadStream& stream, + DynamicBuffer& buffer, + message>& msg, + error_code& ec); + +/** Read a complete message from a stream asynchronously. + + This function is used to asynchronously read a complete message from a + stream 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 read. + + @li An error occurs. + + This operation is implemented in terms of one or more calls to + the stream's `async_read_some` function, and is known as a + composed operation. 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 message being read. This additional data is stored + in the dynamic buffer, which must be retained for subsequent reads. + + If the stream returns the error `boost::asio::error::eof` indicating the + end of file during a read, the error returned from this function will be: + + @li @ref error::end_of_stream if no octets were parsed, or + + @li @ref error::partial_message if any octets were parsed but the + message was incomplete, otherwise: + + @li A successful result. A subsequent attempt to read will + return @ref error::end_of_stream + + @param stream The stream from which the data is to be read. + The type must support the @b AsyncReadStream concept. + + @param buffer A @b DynamicBuffer holding additional bytes + read by the implementation from the stream. This is both + an input and an output parameter; on entry, any data in the + dynamic buffer's input sequence will be given to the parser + first. + + @param msg An object in which to store the message contents. + This object should not have previous contents, otherwise + the behavior is undefined. + The type must be @b MoveAssignable and @b MoveConstructible. + + The object must remain valid at least until the + handler is called; ownership is not transferred. @param handler The handler to be called when the operation completes. Copies will be made of the handler as required. @@ -282,18 +732,22 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf, this function. Invocation of the handler will be performed in a manner equivalent to using `boost::asio::io_service::post`. */ -template -#if GENERATING_DOCS -void_or_deduced +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Body, class Allocator, + class ReadHandler> +#if BEAST_DOXYGEN + void_or_deduced #else -typename async_completion< - ReadHandler, void(error_code)>::result_type +async_return_type< + ReadHandler, void(error_code)> #endif -async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf, - message& msg, - ReadHandler&& handler); +async_read( + AsyncReadStream& stream, + DynamicBuffer& buffer, + message>& msg, + ReadHandler&& handler); } // http } // beast diff --git a/include/beast/http/reason.hpp b/include/beast/http/reason.hpp deleted file mode 100644 index fce97e9d1f..0000000000 --- a/include/beast/http/reason.hpp +++ /dev/null @@ -1,85 +0,0 @@ -// -// 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_HTTP_REASON_HPP -#define BEAST_HTTP_REASON_HPP - -#include - -namespace beast { -namespace http { - -namespace detail { - -template -char const* -reason_string(int status) -{ - switch(status) - { - case 100: return "Continue"; - case 101: return "Switching Protocols"; - case 200: return "OK"; - case 201: return "Created"; - case 202: return "Accepted"; - case 203: return "Non-Authoritative Information"; - case 204: return "No Content"; - case 205: return "Reset Content"; - case 206: return "Partial Content"; - case 300: return "Multiple Choices"; - case 301: return "Moved Permanently"; - case 302: return "Found"; - case 303: return "See Other"; - case 304: return "Not Modified"; - case 305: return "Use Proxy"; - case 307: return "Temporary Redirect"; - case 400: return "Bad Request"; - case 401: return "Unauthorized"; - case 402: return "Payment Required"; - case 403: return "Forbidden"; - case 404: return "Not Found"; - case 405: return "Method Not Allowed"; - case 406: return "Not Acceptable"; - case 407: return "Proxy Authentication Required"; - case 408: return "Request Timeout"; - case 409: return "Conflict"; - case 410: return "Gone"; - case 411: return "Length Required"; - case 412: return "Precondition Failed"; - case 413: return "Request Entity Too Large"; - case 414: return "Request-URI Too Long"; - case 415: return "Unsupported Media Type"; - case 416: return "Requested Range Not Satisfiable"; - case 417: return "Expectation Failed"; - case 500: return "Internal Server Error"; - case 501: return "Not Implemented"; - case 502: return "Bad Gateway"; - case 503: return "Service Unavailable"; - case 504: return "Gateway Timeout"; - case 505: return "HTTP Version Not Supported"; - - case 306: return ""; - default: - break; - } - return ""; -} - -} // detail - -/** Returns the text for a known status code integer. */ -inline -char const* -reason_string(int status) -{ - return detail::reason_string(status); -} - -} // http -} // beast - -#endif diff --git a/include/beast/http/rfc7230.hpp b/include/beast/http/rfc7230.hpp index 1f73194db1..e2d3f3fb29 100644 --- a/include/beast/http/rfc7230.hpp +++ b/include/beast/http/rfc7230.hpp @@ -10,13 +10,14 @@ #include #include +#include namespace beast { namespace http { -/** A list of parameters in a HTTP extension field value. +/** A list of parameters in an HTTP extension field value. - This container allows iteration of the parameter list in a HTTP + This container allows iteration of the parameter list in an HTTP extension. The parameter list is a series of name/value pairs with each pair starting with a semicolon. The value is optional. @@ -48,7 +49,7 @@ namespace http { */ class param_list { - boost::string_ref s_; + string_view s_; public: /** The type of each element in the list. @@ -58,10 +59,10 @@ public: be empty). */ using value_type = - std::pair; + std::pair; /// A constant iterator to the list -#if GENERATING_DOCS +#if BEAST_DOXYGEN using const_iterator = implementation_defined; #else class const_iterator; @@ -76,7 +77,7 @@ public: must remain valid for the lifetime of the container. */ explicit - param_list(boost::string_ref const& s) + param_list(string_view s) : s_(s) { } @@ -98,7 +99,7 @@ public: /** A list of extensions in a comma separated HTTP field value. - This container allows iteration of the extensions in a HTTP + This container allows iteration of the extensions in an HTTP field value. The extension list is a comma separated list of token parameter list pairs. @@ -136,9 +137,9 @@ public: */ class ext_list { - using iter_type = boost::string_ref::const_iterator; + using iter_type = string_view::const_iterator; - boost::string_ref s_; + string_view s_; public: /** The type of each element in the list. @@ -147,10 +148,10 @@ public: second element of the pair is an iterable container holding the extension's name/value parameters. */ - using value_type = std::pair; + using value_type = std::pair; /// A constant iterator to the list -#if GENERATING_DOCS +#if BEAST_DOXYGEN using const_iterator = implementation_defined; #else class const_iterator; @@ -162,7 +163,7 @@ public: must remain valid for the lifetime of the container. */ explicit - ext_list(boost::string_ref const& s) + ext_list(string_view s) : s_(s) { } @@ -229,16 +230,16 @@ public: */ class token_list { - using iter_type = boost::string_ref::const_iterator; + using iter_type = string_view::const_iterator; - boost::string_ref s_; + string_view s_; public: /// The type of each element in the token list. - using value_type = boost::string_ref; + using value_type = string_view; /// A constant iterator to the list -#if GENERATING_DOCS +#if BEAST_DOXYGEN using const_iterator = implementation_defined; #else class const_iterator; @@ -250,7 +251,7 @@ public: must remain valid for the lifetime of the container. */ explicit - token_list(boost::string_ref const& s) + token_list(string_view s) : s_(s) { } @@ -276,6 +277,46 @@ public: exists(T const& s); }; +/** A list of tokens in a comma separated HTTP field value. + + This container allows iteration of a list of items in a + header field value. The input is a comma separated list of + tokens. + + If a parsing error is encountered while iterating the string, + the behavior of the container will be as if a string containing + only characters up to but excluding the first invalid character + was used to construct the list. + + @par BNF + @code + token-list = *( "," OWS ) token *( OWS "," [ OWS token ] ) + @endcode + + To use this class, construct with the string to be parsed and + then use `begin` and `end`, or range-for to iterate each item: + + @par Example + @code + for(auto const& token : token_list{"apple, pear, banana"}) + std::cout << token << "\n"; + @endcode +*/ +using opt_token_list = + detail::basic_parsed_list< + detail::opt_token_list_policy>; + +/** Returns `true` if a parsed list is parsed without errors. + + This function iterates a single pass through a parsed list + and returns `true` if there were no parsing errors, else + returns `false`. +*/ +template +bool +validate_list(detail::basic_parsed_list< + Policy> const& list); + } // http } // beast diff --git a/include/beast/http/serializer.hpp b/include/beast/http/serializer.hpp new file mode 100644 index 0000000000..34bf1d83bf --- /dev/null +++ b/include/beast/http/serializer.hpp @@ -0,0 +1,502 @@ +// +// 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_HTTP_SERIALIZER_HPP +#define BEAST_HTTP_SERIALIZER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef BEAST_NO_BIG_VARIANTS +# if defined(BOOST_GCC) && BOOST_GCC < 50000 && BOOST_VERSION < 106400 +# define BEAST_NO_BIG_VARIANTS +# endif +#endif + +namespace beast { +namespace http { + +/** A chunk decorator which does nothing. + + When selected as a chunk decorator, objects of this type + affect the output of messages specifying chunked + transfer encodings as follows: + + @li chunk headers will have empty chunk extensions, and + + @li final chunks will have an empty set of trailers. + + @see @ref serializer +*/ +struct no_chunk_decorator +{ + template + string_view + operator()(ConstBufferSequence const&) const + { + return {}; + } + + string_view + operator()(boost::asio::null_buffers) const + { + return {}; + } +}; + +/** Provides buffer oriented HTTP message serialization functionality. + + An object of this type is used to serialize a complete + HTTP message into a sequence of octets. To use this class, + construct an instance with the message to be serialized. + + The implementation will automatically perform chunk encoding + if the contents of the message indicate that chunk encoding + is required. If the semantics of the message indicate that + the connection should be closed after the message is sent, the + function @ref keep_alive will return `true`. + + Upon construction, an optional chunk decorator may be + specified. This decorator is a function object called with + each buffer sequence of the body when the chunked transfer + encoding is indicate in the message header. The decorator + will be called with an empty buffer sequence (actually + the type `boost::asio::null_buffers`) to indicate the + final chunk. The decorator may return a string which forms + the chunk extension for chunks, and the field trailers + for the final chunk. + + In C++11 the decorator must be declared as a class or + struct with a templated operator() thusly: + + @code + // The implementation guarantees that operator() + // will be called only after the view returned by + // any previous calls to operator() are no longer + // needed. The decorator instance is intended to + // manage the lifetime of the storage for all returned + // views. + // + struct decorator + { + // Returns the chunk-extension for each chunk, + // or an empty string for no chunk extension. The + // buffer must include the leading semicolon (";") + // and follow the format for chunk extensions defined + // in rfc7230. + // + template + string_view + operator()(ConstBufferSequence const&) const; + + // Returns a set of field trailers for the final chunk. + // Each field should be formatted according to rfc7230 + // including the trailing "\r\n" for each field. If + // no trailers are indicated, an empty string is returned. + // + string_view + operator()(boost::asio::null_buffers) const; + }; + @endcode + + @tparam isRequest `true` if the message is a request. + + @tparam Body The body type of the message. + + @tparam Fields The type of fields in the message. + + @tparam ChunkDecorator The type of chunk decorator to use. +*/ +template< + bool isRequest, + class Body, + class Fields = fields, + class ChunkDecorator = no_chunk_decorator> +class serializer +{ +public: + static_assert(is_body::value, + "Body requirements not met"); + + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + + /** The type of message this serializer uses + + This may be const or non-const depending on the + implementation of the corresponding @b BodyReader. + */ +#if BEAST_DOXYGEN + using value_type = implementation_defined; +#else + using value_type = + typename std::conditional< + std::is_constructible&>::value && + ! std::is_constructible const&>::value, + message, + message const>::type; +#endif + +private: + enum + { + do_construct = 0, + + do_init = 10, + do_header_only = 20, + do_header = 30, + do_body = 40, + + do_init_c = 50, + do_header_only_c = 60, + do_header_c = 70, + do_body_c = 80, + do_final_c = 90, + #ifndef BEAST_NO_BIG_VARIANTS + do_body_final_c = 100, + do_all_c = 110, + #endif + + do_complete = 120 + }; + + void frdinit(std::true_type); + void frdinit(std::false_type); + + template + void + do_visit(error_code& ec, Visit& visit); + + using reader = typename Body::reader; + + using cb1_t = consuming_buffers; // header + using pcb1_t = buffer_prefix_view; + + using cb2_t = consuming_buffers>; // body + using pcb2_t = buffer_prefix_view; + + using cb3_t = consuming_buffers< + typename reader::const_buffers_type>; // body + using pcb3_t = buffer_prefix_view; + + using cb4_t = consuming_buffers>; // crlf + using pcb4_t = buffer_prefix_view; + + using cb5_t = consuming_buffers>; // crlf + using pcb5_t = buffer_prefix_view; + +#ifndef BEAST_NO_BIG_VARIANTS + using cb6_t = consuming_buffers>; // crlf + using pcb6_t = buffer_prefix_view; + + using cb7_t = consuming_buffers>; // crlf + using pcb7_t = buffer_prefix_view; +#endif + + using cb8_t = consuming_buffers>; // crlf + using pcb8_t = buffer_prefix_view; + + value_type& m_; + reader rd_; + boost::optional frd_; + boost::variant v_; + boost::variant pv_; + std::size_t limit_ = + (std::numeric_limits::max)(); + int s_ = do_construct; + bool split_ = false; + bool header_done_ = false; + bool chunked_; + bool keep_alive_; + bool more_; + ChunkDecorator d_; + +public: + /** Constructor + + The implementation guarantees that the message passed on + construction will not be accessed until the first call to + @ref next. This allows the message to be lazily created. + For example, if the header is filled in before serialization. + + @param msg A reference to the message to serialize, which must + remain valid for the lifetime of the serializer. Depending on + the type of Body used, this may or may not be a `const` reference. + + @note This function participates in overload resolution only if + Body::reader is constructible from a `const` message reference. + */ + explicit + serializer(value_type& msg); + + /** Constructor + + The implementation guarantees that the message passed on + construction will not be accessed until the first call to + @ref next. This allows the message to be lazily created. + For example, if the header is filled in before serialization. + + @param msg A reference to the message to serialize, which must + remain valid for the lifetime of the serializer. Depending on + the type of Body used, this may or may not be a `const` reference. + + @param decorator The decorator to use. + + @note This function participates in overload resolution only if + Body::reader is constructible from a `const` message reference. + */ + explicit + serializer(value_type& msg, ChunkDecorator const& decorator); + + /// Returns the message being serialized + value_type& + get() + { + return m_; + } + + /** Provides access to the associated @b BodyReader + + This function provides access to the instance of the reader + associated with the body and created by the serializer + upon construction. The behavior of accessing this object + is defined by the specification of the particular reader + and its associated body. + + @return A reference to the reader. + */ + reader& + reader_impl() + { + return rd_; + } + + /// Returns the serialized buffer size limit + std::size_t + limit() + { + return limit_; + } + + /** Set the serialized buffer size limit + + This function adjusts the limit on the maximum size of the + buffers passed to the visitor. The new size limit takes effect + in the following call to @ref next. + + The default is no buffer size limit. + + @param limit The new buffer size limit. If this number + is zero, the size limit is removed. + */ + void + limit(std::size_t limit) + { + limit_ = limit > 0 ? limit : + (std::numeric_limits::max)(); + } + + /** Returns `true` if we will pause after writing the complete header. + */ + bool + split() + { + return split_; + } + + /** Set whether the header and body are written separately. + + When the split feature is enabled, the implementation will + write only the octets corresponding to the serialized header + first. If the header has already been written, this function + will have no effect on output. + */ + void + split(bool v) + { + split_ = v; + } + + /** Return `true` if serialization of the header is complete. + + This function indicates whether or not all buffers containing + serialized header octets have been retrieved. + */ + bool + is_header_done() + { + return header_done_; + } + + /** Return `true` if serialization is complete. + + The operation is complete when all octets corresponding + to the serialized representation of the message have been + successfully retrieved. + */ + bool + is_done() + { + return s_ == do_complete; + } + + /** Return `true` if the serializer will apply chunk-encoding. + + This function may only be called if @ref is_header_done + would return `true`. + */ + bool + chunked() + { + return chunked_; + } + + /** Return `true` if Connection: keep-alive semantic is indicated. + + This function returns `true` if the semantics of the + message indicate that the connection should be kept open + after the serialized message has been transmitted. The + value depends on the HTTP version of the message, + the tokens in the Connection header, and the metadata + describing the payload body. + + Depending on the payload body, the end of the message may + be indicated by connection closuire. In order for the + recipient (if any) to receive a complete message, the + underlying stream or network connection must be closed + when this function returns `false`. + + This function may only be called if @ref is_header_done + would return `true`. + */ + bool + keep_alive() + { + return keep_alive_; + } + + /** Returns the next set of buffers in the serialization. + + This function will attempt to call the `visit` function + object with a @b ConstBufferSequence of unspecified type + representing the next set of buffers in the serialization + of the message represented by this object. + + If there are no more buffers in the serialization, the + visit function will not be called. In this case, no error + will be indicated, and the function @ref is_done will + return `true`. + + @param ec Set to the error, if any occurred. + + @param visit The function to call. The equivalent function + signature of this object must be: + @code + template + void visit(error_code&, ConstBufferSequence const&); + @endcode + The function is not copied, if no error occurs it will be + invoked before the call to @ref next returns. + + */ + template + void + next(error_code& ec, Visit&& visit); + + /** Consume buffer octets in the serialization. + + This function should be called after one or more octets + contained in the buffers provided in the prior call + to @ref next have been used. + + After a call to @ref consume, callers should check the + return value of @ref is_done to determine if the entire + message has been serialized. + + @param n The number of octets to consume. This number must + be greater than zero and no greater than the number of + octets in the buffers provided in the prior call to @ref next. + */ + void + consume(std::size_t n); +}; + +/// A serializer for HTTP/1 requests +template< + class Body, + class Fields = fields, + class ChunkDecorator = no_chunk_decorator> +using request_serializer = serializer; + +/// A serializer for HTTP/1 responses +template< + class Body, + class Fields = fields, + class ChunkDecorator = no_chunk_decorator> +using response_serializer = serializer; + +} // http +} // beast + +#include + +#endif diff --git a/include/beast/http/span_body.hpp b/include/beast/http/span_body.hpp new file mode 100644 index 0000000000..8e83cde201 --- /dev/null +++ b/include/beast/http/span_body.hpp @@ -0,0 +1,167 @@ +// +// 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_HTTP_SPAN_BODY_HPP +#define BEAST_HTTP_SPAN_BODY_HPP + +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** A @b Body using @ref span + + This body uses @ref span as a memory-based container for + holding message payloads. The container represents a + non-owning reference to a continguous area of memory. + Messages using this body type may be serialized and + parsed. + + Unlike @ref buffer_body, only one buffer may be provided + during a parse or serialize operation. +*/ +template +struct span_body +{ +private: + static_assert(std::is_pod::value, + "POD requirements not met"); + +public: + /** The type of container used for the body + + This determines the type of @ref message::body + when this body type is used with a message container. + */ + using value_type = span; + + /** Returns the payload size of the body + + When this body is used with @ref message::prepare_payload, + the Content-Length will be set to the payload size, and + any chunked Transfer-Encoding will be removed. + */ + static + std::uint64_t + size(value_type const& body) + { + return body.size(); + } + + /** The algorithm for serializing the body + + Meets the requirements of @b BodyReader. + */ +#if BEAST_DOXYGEN + using reader = implementation_defined; +#else + class reader + { + value_type const& body_; + + public: + using const_buffers_type = + boost::asio::const_buffers_1; + + template + explicit + reader(message const& msg) + : body_(msg.body) + { + } + + void + init(error_code& ec) + { + ec.assign(0, ec.category()); + } + + boost::optional> + get(error_code& ec) + { + ec.assign(0, ec.category()); + return {{ + { body_.data(), + body_.size() * sizeof(typename + value_type::value_type)}, + false}}; + } + }; +#endif + + /** The algorithm for parsing the body + + Meets the requirements of @b BodyReader. + */ +#if BEAST_DOXYGEN + using writer = implementation_defined; +#else + class writer + { + value_type& body_; + + public: + template + explicit + writer(message& m) + : body_(m.body) + { + } + + void + init(boost::optional< + std::uint64_t> const& length, error_code& ec) + { + if(length && *length > body_.size()) + { + ec = error::buffer_overflow; + return; + } + ec.assign(0, ec.category()); + } + + template + std::size_t + put(ConstBufferSequence const& buffers, + error_code& ec) + { + using boost::asio::buffer_size; + using boost::asio::buffer_copy; + auto const n = buffer_size(buffers); + auto const len = body_.size(); + if(n > len) + { + ec = error::buffer_overflow; + return 0; + } + ec.assign(0, ec.category()); + buffer_copy(boost::asio::buffer( + body_.data(), n), buffers); + body_ = value_type{ + body_.data() + n, body_.size() - n}; + return n; + } + + void + finish(error_code& ec) + { + ec.assign(0, ec.category()); + } + }; +#endif +}; + +} // http +} // beast + +#endif diff --git a/include/beast/http/status.hpp b/include/beast/http/status.hpp new file mode 100644 index 0000000000..b202aa7bee --- /dev/null +++ b/include/beast/http/status.hpp @@ -0,0 +1,163 @@ +// +// 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_HTTP_STATUS_HPP +#define BEAST_HTTP_STATUS_HPP + +#include +#include +#include + +namespace beast { +namespace http { + +enum class status : unsigned +{ + /** An unknown status-code. + + This value indicates that the value for the status code + is not in the list of commonly recognized status codes. + Callers interested in the exactly value should use the + interface which provides the raw integer. + */ + unknown = 0, + + continue_ = 100, + switching_protocols = 101, + processing = 102, + + ok = 200, + created = 201, + accepted = 202, + non_authoritative_information = 203, + no_content = 204, + reset_content = 205, + partial_content = 206, + multi_status = 207, + already_reported = 208, + im_used = 226, + + multiple_choices = 300, + moved_permanently = 301, + found = 302, + see_other = 303, + not_modified = 304, + use_proxy = 305, + temporary_redirect = 307, + permanent_redirect = 308, + + bad_request = 400, + unauthorized = 401, + payment_required = 402, + forbidden = 403, + not_found = 404, + method_not_allowed = 405, + not_acceptable = 406, + proxy_authentication_required = 407, + request_timeout = 408, + conflict = 409, + gone = 410, + length_required = 411, + precondition_failed = 412, + payload_too_large = 413, + uri_too_long = 414, + unsupported_media_type = 415, + range_not_satisfiable = 416, + expectation_failed = 417, + misdirected_request = 421, + unprocessable_entity = 422, + locked = 423, + failed_dependency = 424, + upgrade_required = 426, + precondition_required = 428, + too_many_requests = 429, + request_header_fields_too_large = 431, + connection_closed_without_response = 444, + unavailable_for_legal_reasons = 451, + client_closed_request = 499, + + internal_server_error = 500, + not_implemented = 501, + bad_gateway = 502, + service_unavailable = 503, + gateway_timeout = 504, + http_version_not_supported = 505, + variant_also_negotiates = 506, + insufficient_storage = 507, + loop_detected = 508, + not_extended = 510, + network_authentication_required = 511, + network_connect_timeout_error = 599 +}; + +/** Represents the class of a status-code. +*/ +enum class status_class : unsigned +{ + /// Unknown status-class + unknown = 0, + + /// The request was received, continuing processing. + informational = 1, + + /// The request was successfully received, understood, and accepted. + successful = 2, + + /// Further action needs to be taken in order to complete the request. + redirection = 3, + + /// The request contains bad syntax or cannot be fulfilled. + client_error = 4, + + /// The server failed to fulfill an apparently valid request. + server_error = 5, +}; + +/** Converts the integer to a known status-code. + + If the integer does not match a known status code, + @ref status::unknown is returned. +*/ +status +int_to_status(unsigned v); + +/** Convert an integer to a status_class. + + @param v The integer representing a status code. + + @return The status class. If the integer does not match + a known status class, @ref status_class::unknown is returned. +*/ +status_class +to_status_class(unsigned v); + +/** Convert a status_code to a status_class. + + @param v The status code to convert. + + @return The status class. +*/ +status_class +to_status_class(status v); + +/** Returns the obsolete reason-phrase text for a status code. + + @param v The status code to use. +*/ +string_view +obsolete_reason(status v); + +/// Outputs the standard reason phrase of a status code to a stream. +std::ostream& +operator<<(std::ostream&, status); + +} // http +} // beast + +#include + +#endif diff --git a/include/beast/http/streambuf_body.hpp b/include/beast/http/streambuf_body.hpp deleted file mode 100644 index 974a3f3caf..0000000000 --- a/include/beast/http/streambuf_body.hpp +++ /dev/null @@ -1,27 +0,0 @@ -// -// 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_HTTP_STREAMBUF_BODY_HPP -#define BEAST_HTTP_STREAMBUF_BODY_HPP - -#include -#include -#include - -namespace beast { -namespace http { - -/** A message body represented by a @ref streambuf - - Meets the requirements of @b `Body`. -*/ -using streambuf_body = basic_dynabuf_body; - -} // http -} // beast - -#endif diff --git a/include/beast/http/string_body.hpp b/include/beast/http/string_body.hpp index 707709c113..cdeaec7c76 100644 --- a/include/beast/http/string_body.hpp +++ b/include/beast/http/string_body.hpp @@ -9,92 +9,181 @@ #define BEAST_HTTP_STRING_BODY_HPP #include -#include +#include #include #include #include +#include +#include +#include #include +#include #include +#include namespace beast { namespace http { -/** A Body represented by a std::string. +/** A @b Body using `std::basic_string` - Meets the requirements of @b `Body`. + This body uses `std::basic_string` as a memory-based container + for holding message payloads. Messages using this body type + may be serialized and parsed. */ -struct string_body +template< + class CharT, + class Traits = std::char_traits, + class Allocator = std::allocator> +struct basic_string_body { - /// The type of the `message::body` member - using value_type = std::string; - -#if GENERATING_DOCS private: -#endif + static_assert( + std::is_integral::value && + sizeof(CharT) == 1, + "CharT requirements not met"); - class reader +public: + /** The type of container used for the body + + This determines the type of @ref message::body + when this body type is used with a message container. + */ + using value_type = + std::basic_string; + + /** Returns the payload size of the body + + When this body is used with @ref message::prepare_payload, + the Content-Length will be set to the payload size, and + any chunked Transfer-Encoding will be removed. + */ + static + std::uint64_t + size(value_type const& body) { - value_type& s_; + return body.size(); + } - public: - template - explicit - reader(message& m) noexcept - : s_(m.body) - { - } + /** The algorithm for serializing the body - void - init(error_code&) noexcept - { - } - - void - write(void const* data, - std::size_t size, error_code&) noexcept - { - auto const n = s_.size(); - s_.resize(n + size); - std::memcpy(&s_[n], data, size); - } - }; - - class writer + Meets the requirements of @b BodyReader. + */ +#if BEAST_DOXYGEN + using reader = implementation_defined; +#else + class reader { value_type const& body_; public: + using const_buffers_type = + boost::asio::const_buffers_1; + template explicit - writer(message< - isRequest, string_body, Fields> const& msg) noexcept + reader(message const& msg) : body_(msg.body) { } void - init(error_code& ec) noexcept + init(error_code& ec) { - beast::detail::ignore_unused(ec); + ec.assign(0, ec.category()); } - std::uint64_t - content_length() const noexcept + boost::optional> + get(error_code& ec) { - return body_.size(); - } - - template - bool - write(error_code&, WriteFunction&& wf) noexcept - { - wf(boost::asio::buffer(body_)); - return true; + ec.assign(0, ec.category()); + return {{const_buffers_type{ + body_.data(), body_.size()}, false}}; } }; +#endif + + /** The algorithm for parsing the body + + Meets the requirements of @b BodyReader. + */ +#if BEAST_DOXYGEN + using writer = implementation_defined; +#else + class writer + { + value_type& body_; + + public: + template + explicit + writer(message& m) + : body_(m.body) + { + } + + void + init(boost::optional< + std::uint64_t> const& length, error_code& ec) + { + if(length) + { + if(*length > ( + std::numeric_limits::max)()) + { + ec = error::buffer_overflow; + return; + } + try + { + body_.reserve( + static_cast(*length)); + } + catch(std::exception const&) + { + ec = error::buffer_overflow; + return; + } + } + ec.assign(0, ec.category()); + } + + template + std::size_t + put(ConstBufferSequence const& buffers, + error_code& ec) + { + using boost::asio::buffer_size; + using boost::asio::buffer_copy; + auto const n = buffer_size(buffers); + auto const len = body_.size(); + try + { + body_.resize(len + n); + } + catch(std::exception const&) + { + ec = error::buffer_overflow; + return 0; + } + ec.assign(0, ec.category()); + return buffer_copy(boost::asio::buffer( + &body_[0] + len, n), buffers); + } + + void + finish(error_code& ec) + { + ec.assign(0, ec.category()); + } + }; +#endif }; +/// A @b Body using `std::string` +using string_body = basic_string_body; + } // http } // beast diff --git a/include/beast/http/type_traits.hpp b/include/beast/http/type_traits.hpp new file mode 100644 index 0000000000..46f5674a96 --- /dev/null +++ b/include/beast/http/type_traits.hpp @@ -0,0 +1,181 @@ +// +// 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_HTTP_TYPE_TRAITS_HPP +#define BEAST_HTTP_TYPE_TRAITS_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +template +struct message; + +/** Determine if `T` meets the requirements of @b Body. + + This metafunction is equivalent to `std::true_type` + if `T` has a nested type named `value_type`. + + @tparam T The body type to test. + + @par Example + @code + template + void check_body(message const&) + { + static_assert(is_body::value, + "Body requirements not met"); + } + @endcode +*/ +template +#if BEAST_DOXYGEN +struct is_body : std::integral_constant{}; +#else +using is_body = detail::has_value_type; +#endif + +/** Determine if a @b Body type has a reader. + + This metafunction is equivalent to `std::true_type` if: + + @li `T` has a nested type named `reader` + + @li The nested type meets the requirements of @b BodyReader. + + @tparam T The body type to test. + + @par Example + @code + template + void check_can_serialize(message const&) + { + static_assert(is_body_reader::value, + "Cannot serialize Body, no reader"); + } + @endcode +*/ +#if BEAST_DOXYGEN +template +struct is_body_reader : std::integral_constant {}; +#else +template +struct is_body_reader : std::false_type {}; + +template +struct is_body_reader().init(std::declval()), + std::declval>&>() = + std::declval().get(std::declval()), + (void)0)>> : std::integral_constant::value && + std::is_constructible&>::value && + std::is_constructible&>::value + > {}; +#endif + +/** Determine if a @b Body type has a writer. + + This metafunction is equivalent to `std::true_type` if: + + @li `T` has a nested type named `writer` + + @li The nested type meets the requirements of @b BodyWriter. + + @tparam T The body type to test. + + @par Example + @code + template + void check_can_parse(message&) + { + static_assert(is_body_writer::value, + "Cannot parse Body, no writer"); + } + @endcode +*/ +#if BEAST_DOXYGEN +template +struct is_body_writer : std::integral_constant {}; +#else +template +struct is_body_writer : std::false_type {}; + +template +struct is_body_writer().init( + boost::optional(), + std::declval()), + std::declval() = + std::declval().put( + std::declval(), + std::declval()), + std::declval().finish( + std::declval()), + (void)0)>> : std::integral_constant&>::value && + std::is_constructible&>::value + > +{ +}; +#endif + +/** Determine if `T` meets the requirements of @b Fields + + @tparam T The body type to test. + + @par Example + + Use with `static_assert`: + + @code + template + void f(message const&) + { + static_assert(is_fields::value, + "Fields requirements not met"); + ... + @endcode + + Use with `std::enable_if` (SFINAE): + + @code + template + typename std::enable_if::value>::type + f(message const&); + @endcode +*/ +#if BEAST_DOXYGEN +template +struct is_fields : std::integral_constant {}; +#else +template +using is_fields = typename detail::is_fields_helper::type; +#endif + +} // http +} // beast + +#endif diff --git a/include/beast/http/vector_body.hpp b/include/beast/http/vector_body.hpp new file mode 100644 index 0000000000..36a19c4255 --- /dev/null +++ b/include/beast/http/vector_body.hpp @@ -0,0 +1,182 @@ +// +// 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_HTTP_VECTOR_BODY_HPP +#define BEAST_HTTP_VECTOR_BODY_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** A @b Body using `std::vector` + + This body uses `std::vector` as a memory-based container + for holding message payloads. Messages using this body type + may be serialized and parsed. +*/ +template> +struct vector_body +{ +private: + static_assert(sizeof(T) == 1 && + std::is_integral::value, + "T requirements not met"); + +public: + /** The type of container used for the body + + This determines the type of @ref message::body + when this body type is used with a message container. + */ + using value_type = std::vector; + + /** Returns the payload size of the body + + When this body is used with @ref message::prepare_payload, + the Content-Length will be set to the payload size, and + any chunked Transfer-Encoding will be removed. + */ + static + std::uint64_t + size(value_type const& body) + { + return body.size(); + } + + /** The algorithm for serializing the body + + Meets the requirements of @b BodyReader. + */ +#if BEAST_DOXYGEN + using reader = implementation_defined; +#else + class reader + { + value_type const& body_; + + public: + using const_buffers_type = + boost::asio::const_buffers_1; + + template + explicit + reader(message const& msg) + : body_(msg.body) + { + } + + void + init(error_code& ec) + { + ec.assign(0, ec.category()); + } + + boost::optional> + get(error_code& ec) + { + ec.assign(0, ec.category()); + return {{const_buffers_type{ + body_.data(), body_.size()}, false}}; + } + }; +#endif + + /** The algorithm for parsing the body + + Meets the requirements of @b BodyReader. + */ +#if BEAST_DOXYGEN + using writer = implementation_defined; +#else + class writer + { + value_type& body_; + + public: + template + explicit + writer(message& m) + : body_(m.body) + { + } + + void + init(boost::optional< + std::uint64_t> const& length, error_code& ec) + { + if(length) + { + if(*length > ( + std::numeric_limits::max)()) + { + ec = error::buffer_overflow; + return; + } + try + { + body_.reserve( + static_cast(*length)); + } + catch(std::exception const&) + { + ec = error::buffer_overflow; + return; + } + } + ec.assign(0, ec.category()); + } + + template + std::size_t + put(ConstBufferSequence const& buffers, + error_code& ec) + { + using boost::asio::buffer_size; + using boost::asio::buffer_copy; + auto const n = buffer_size(buffers); + auto const len = body_.size(); + try + { + body_.resize(len + n); + } + catch(std::exception const&) + { + ec = error::buffer_overflow; + return 0; + } + ec.assign(0, ec.category()); + return buffer_copy(boost::asio::buffer( + &body_[0] + len, n), buffers); + } + + void + finish(error_code& ec) + { + ec.assign(0, ec.category()); + } + }; +#endif +}; + +} // http +} // beast + +#endif diff --git a/include/beast/http/verb.hpp b/include/beast/http/verb.hpp new file mode 100644 index 0000000000..679af1a65e --- /dev/null +++ b/include/beast/http/verb.hpp @@ -0,0 +1,153 @@ +// +// 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_HTTP_VERB_HPP +#define BEAST_HTTP_VERB_HPP + +#include +#include +#include + +namespace beast { +namespace http { + +/** HTTP request method verbs + + Each verb corresponds to a particular method string + used in HTTP request messages. +*/ +enum class verb +{ + /** An unknown method. + + This value indicates that the request method string is not + one of the recognized verbs. Callers interested in the method + should use an interface which returns the original string. + */ + unknown = 0, + + /// The DELETE method deletes the specified resource + delete_, + + /** The GET method requests a representation of the specified resource. + + Requests using GET should only retrieve data and should have no other effect. + */ + get, + + /** The HEAD method asks for a response identical to that of a GET request, but without the response body. + + This is useful for retrieving meta-information written in response + headers, without having to transport the entire content. + */ + head, + + /** The POST method requests that the server accept the entity enclosed in the request as a new subordinate of the web resource identified by the URI. + + The data POSTed might be, for example, an annotation for existing + resources; a message for a bulletin board, newsgroup, mailing list, + or comment thread; a block of data that is the result of submitting + a web form to a data-handling process; or an item to add to a database + */ + post, + + /** The PUT method requests that the enclosed entity be stored under the supplied URI. + + If the URI refers to an already existing resource, it is modified; + if the URI does not point to an existing resource, then the server + can create the resource with that URI. + */ + put, + + /** The CONNECT method converts the request connection to a transparent TCP/IP tunnel. + + This is usually to facilitate SSL-encrypted communication (HTTPS) + through an unencrypted HTTP proxy. + */ + connect, + + /** The OPTIONS method returns the HTTP methods that the server supports for the specified URL. + + This can be used to check the functionality of a web server by requesting + '*' instead of a specific resource. + */ + options, + + /** The TRACE method echoes the received request so that a client can see what (if any) changes or additions have been made by intermediate servers. + */ + trace, + + // WebDAV + + copy, + lock, + mkcol, + move, + propfind, + proppatch, + search, + unlock, + bind, + rebind, + unbind, + acl, + + // subversion + + report, + mkactivity, + checkout, + merge, + + // upnp + + msearch, + notify, + subscribe, + unsubscribe, + + // RFC-5789 + + patch, + purge, + + // CalDAV + + mkcalendar, + + // RFC-2068, section 19.6.1.2 + + link, + unlink +}; + +/** Converts a string to the request method verb. + + If the string does not match a known request method, + @ref verb::unknown is returned. +*/ +verb +string_to_verb(string_view s); + +/// Returns the text representation of a request method verb. +string_view +to_string(verb v); + +/// Write the text for a request method verb to an output stream. +inline +std::ostream& +operator<<(std::ostream& os, verb v) +{ + return os << to_string(v); +} + +} // http +} // beast + +#include + +#endif diff --git a/include/beast/http/write.hpp b/include/beast/http/write.hpp index b190e2e627..36fe0c00fb 100644 --- a/include/beast/http/write.hpp +++ b/include/beast/http/write.hpp @@ -9,103 +9,134 @@ #define BEAST_HTTP_WRITE_HPP #include +#include +#include +#include #include +#include +#include #include -#include -#include +#include +#include +#include +#include +#include +#include #include +#include namespace beast { namespace http { -/** Write a HTTP/1 header to a stream. +/** Write part of a message to a stream using a serializer. - This function is used to synchronously write a header to - a stream. The call will block until one of the following - conditions is true: + This function is used to write part of a message to a stream using + a caller-provided HTTP/1 serializer. The call will block until one + of the following conditions is true: + + @li One or more bytes have been transferred. - @li The entire header is written. + @li The function @ref serializer::is_done returns `true` - @li An error occurs. + @li An error occurs on the stream. This operation is implemented in terms of one or more calls to the stream's `write_some` function. - Regardless of the semantic meaning of the header (for example, - specifying "Content-Length: 0" and "Connection: close"), - this function will not return `boost::asio::error::eof`. + The amount of data actually transferred is controlled by the behavior + of the underlying stream, subject to the buffer size limit of the + serializer obtained or set through a call to @ref serializer::limit. + Setting a limit and performing bounded work helps applications set + reasonable timeouts. It also allows application-level flow control + to function correctly. For example when using a TCP/IP based + stream. @param stream The stream to which the data is to be written. - The type must support the @b `SyncWriteStream` concept. + The type must support the @b SyncWriteStream concept. - @param msg The header to write. + @param sr The serializer to use. @throws system_error Thrown on failure. + + @see serializer */ -template +template void -write(SyncWriteStream& stream, - header const& msg); +write_some(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr); -/** Write a HTTP/1 header to a stream. +/** Write part of a message to a stream using a serializer. - This function is used to synchronously write a header to - a stream. The call will block until one of the following - conditions is true: + This function is used to write part of a message to a stream using + a caller-provided HTTP/1 serializer. The call will block until one + of the following conditions is true: + + @li One or more bytes have been transferred. - @li The entire header is written. + @li The function @ref serializer::is_done returns `true` - @li An error occurs. + @li An error occurs on the stream. This operation is implemented in terms of one or more calls to the stream's `write_some` function. - Regardless of the semantic meaning of the header (for example, - specifying "Content-Length: 0" and "Connection: close"), - this function will not return `boost::asio::error::eof`. - + The amount of data actually transferred is controlled by the behavior + of the underlying stream, subject to the buffer size limit of the + serializer obtained or set through a call to @ref serializer::limit. + Setting a limit and performing bounded work helps applications set + reasonable timeouts. It also allows application-level flow control + to function correctly. For example when using a TCP/IP based + stream. + @param stream The stream to which the data is to be written. - The type must support the @b `SyncWriteStream` concept. + The type must support the @b SyncWriteStream concept. - @param msg The header to write. + @param sr The serializer to use. - @param ec Set to the error, if any occurred. + @param ec Set to indicate what error occurred, if any. + + @see @ref async_write_some, @ref serializer */ -template +template void -write(SyncWriteStream& stream, - header const& msg, +write_some(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, error_code& ec); -/** Write a HTTP/1 header asynchronously to a stream. +/** Write part of a message to a stream asynchronously using a serializer. - This function is used to asynchronously write a header to - a stream. The function call always returns immediately. The - asynchronous operation will continue until one of the following - conditions is true: + This function is used to write part of a message to a stream + asynchronously using a caller-provided HTTP/1 serializer. The function + call always returns immediately. The asynchronous operation will continue + until one of the following conditions is true: - @li The entire header is written. + @li One or more bytes have been transferred. - @li An error occurs. + @li The function @ref serializer::is_done returns `true` - This operation is implemented in terms of one or more calls to - the stream's `async_write_some` functions, and is known as a - composed operation. The program must ensure that the - stream performs no other write operations until this operation - completes. + @li An error occurs on the stream. - Regardless of the semantic meaning of the header (for example, - specifying "Content-Length: 0" and "Connection: close"), - this function will not return `boost::asio::error::eof`. + This operation is implemented in terms of zero or more calls to the stream's + `async_write_some` function, and is known as a composed operation. + The program must ensure that the stream performs no other write operations + until this operation completes. + The amount of data actually transferred is controlled by the behavior + of the underlying stream, subject to the buffer size limit of the + serializer obtained or set through a call to @ref serializer::limit. + Setting a limit and performing bounded work helps applications set + reasonable timeouts. It also allows application-level flow control + to function correctly. For example when using a TCP/IP based + stream. + @param stream The stream to which the data is to be written. - The type must support the @b `AsyncWriteStream` concept. + The type must support the @b AsyncWriteStream concept. - @param msg The header to write. The object must remain valid - at least until the completion handler is called; ownership is - not transferred. + @param sr The serializer to use. + The object must remain valid at least until the + handler is called; ownership is not transferred. @param handler The handler to be called when the operation completes. Copies will be made of the handler as required. @@ -117,46 +148,269 @@ write(SyncWriteStream& stream, 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`. + + @see @ref serializer */ template -#if GENERATING_DOCS -void_or_deduced + bool isRequest, class Body, class Fields, + class Decorator, class WriteHandler> +#if BEAST_DOXYGEN + void_or_deduced #else -typename async_completion< - WriteHandler, void(error_code)>::result_type +async_return_type #endif -async_write(AsyncWriteStream& stream, - header const& msg, +async_write_some(AsyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, WriteHandler&& handler); //------------------------------------------------------------------------------ -/** Write a HTTP/1 message to a stream. +/** Write a header to a stream using a serializer. - This function is used to write a message to a stream. The call - will block until one of the following conditions is true: + This function is used to write a header to a stream using a + caller-provided HTTP/1 serializer. The call will block until one + of the following conditions is true: - @li The entire message is written. + @li The function @ref serializer::is_header_done returns `true` @li An error occurs. This operation is implemented in terms of one or more calls to the stream's `write_some` function. - The implementation will automatically perform chunk encoding if - the contents of the message indicate that chunk encoding is required. - If the semantics of the message indicate that the connection should - be closed after the message is sent, the error thrown from this - function will be `boost::asio::error::eof`. + @param stream The stream to which the data is to be written. + The type must support the @b SyncWriteStream concept. + + @param sr The serializer to use. + + @throws system_error Thrown on failure. + + @note The implementation will call @ref serializer::split with + the value `true` on the serializer passed in. + + @see @ref serializer +*/ +template +void +write_header(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr); + +/** Write a header to a stream using a serializer. + + This function is used to write a header to a stream using a + caller-provided HTTP/1 serializer. The call will block until one + of the following conditions is true: + + @li The function @ref serializer::is_header_done returns `true` + + @li An error occurs. + + This operation is implemented in terms of one or more calls + to the stream's `write_some` function. @param stream The stream to which the data is to be written. - The type must support the @b `SyncWriteStream` concept. + The type must support the @b SyncWriteStream concept. + + @param sr The serializer to use. + + @param ec Set to indicate what error occurred, if any. + + @note The implementation will call @ref serializer::split with + the value `true` on the serializer passed in. + + @see @ref serializer +*/ +template +void +write_header(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, + error_code& ec); + +/** Write a header to a stream asynchronously using a serializer. + + This function is used to write a header to a stream asynchronously + using a caller-provided HTTP/1 serializer. The function call always + returns immediately. The asynchronous operation will continue until + one of the following conditions is true: + + @li The function @ref serializer::is_header_done returns `true` + + @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 composed operation. + The program must ensure that the stream performs no other write operations + until this operation completes. + + @param stream The stream to which the data is to be written. + The type must support the @b AsyncWriteStream concept. + + @param sr The serializer to use. + The object must remain valid at least until the + handler is called; ownership is not transferred. + + @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`. + + @note The implementation will call @ref serializer::split with + the value `true` on the serializer passed in. + + @see @ref serializer +*/ +template +#if BEAST_DOXYGEN + void_or_deduced +#else +async_return_type +#endif +async_write_header(AsyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, + WriteHandler&& handler); + +//------------------------------------------------------------------------------ + +/** Write a complete message to a stream using a serializer. + + This function is used to write a complete message to a stream using + a caller-provided HTTP/1 serializer. The call will block until one + of the following conditions is true: + + @li The function @ref serializer::is_done returns `true` + + @li An error occurs. + + This operation is implemented in terms of one or more calls + to the stream's `write_some` function. + + @param stream The stream to which the data is to be written. + The type must support the @b SyncWriteStream concept. + + @param sr The serializer to use. + + @throws system_error Thrown on failure. + + @see @ref serializer +*/ +template +void +write(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr); + +/** Write a complete message to a stream using a serializer. + + This function is used to write a complete message to a stream using + a caller-provided HTTP/1 serializer. The call will block until one + of the following conditions is true: + + @li The function @ref serializer::is_done returns `true` + + @li An error occurs. + + This operation is implemented in terms of one or more calls + to the stream's `write_some` function. + + @param stream The stream to which the data is to be written. + The type must support the @b SyncWriteStream concept. + + @param sr The serializer to use. + + @param ec Set to the error, if any occurred. + + @see @ref serializer +*/ +template +void +write(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, + error_code& ec); + +/** Write a complete message to a stream asynchronously using a serializer. + + This function is used to write a complete message to a stream + asynchronously using a caller-provided HTTP/1 serializer. The + function call always returns immediately. The asynchronous + operation will continue until one of the following conditions is true: + + @li The function @ref serializer::is_done returns `true` + + @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 composed operation. + The program must ensure that the stream performs no other write operations + until this operation completes. + + @param stream The stream to which the data is to be written. + The type must support the @b AsyncWriteStream concept. + + @param sr The serializer to use. + The object must remain valid at least until the + handler is called; ownership is not transferred. + + @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`. + + @see @ref serializer +*/ +template +#if BEAST_DOXYGEN + void_or_deduced +#else +async_return_type +#endif +async_write(AsyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, + WriteHandler&& handler); + +//------------------------------------------------------------------------------ + +/** Write a complete message to a stream. + + This function is used to write a complete message to a stream using + HTTP/1. The call will block 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 one or more calls to the stream's + `write_some` function. 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 SyncWriteStream concept. @param msg The message to write. @throws system_error Thrown on failure. + + @see @ref message */ template @@ -164,30 +418,29 @@ void write(SyncWriteStream& stream, message const& msg); -/** Write a HTTP/1 message on a stream. +/** Write a complete message to a stream. - This function is used to write a message to a stream. The call - will block until one of the following conditions is true: + This function is used to write a complete message to a stream using + HTTP/1. The call will block 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 one or more calls - to the stream's `write_some` function. - - The implementation will automatically perform chunk encoding if - the contents of the message indicate that chunk encoding is required. - If the semantics of the message indicate that the connection should - be closed after the message is sent, the error returned from this - function will be `boost::asio::error::eof`. + This operation is implemented in terms of one or more calls to the stream's + `write_some` function. 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 `SyncWriteStream` concept. + The type must support the @b SyncWriteStream concept. @param msg The message to write. @param ec Set to the error, if any occurred. + + @see @ref message */ template @@ -196,35 +449,31 @@ write(SyncWriteStream& stream, message const& msg, error_code& ec); -/** Write a HTTP/1 message asynchronously to a stream. +/** Write a complete message to a stream asynchronously. - This function is used to asynchronously write a message to - a stream. The function call always returns immediately. The - asynchronous operation will continue until one of the following - conditions is true: + 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 one or more calls to - the stream's `async_write_some` functions, and is known as a - composed operation. The program must ensure that the - stream performs no other write operations until this operation - completes. - - The implementation will automatically perform chunk encoding if - the contents of the message indicate that chunk encoding is required. - If the semantics of the message indicate that the connection should - be closed after the message is sent, the operation will complete with - the error set to `boost::asio::error::eof`. + This operation is implemented in terms of zero or more calls to the stream's + `async_write_some` function, and is known as a composed operation. + 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. + The type must support the @b AsyncWriteStream concept. - @param msg The message to write. The object must remain valid - at least until the completion handler is called; ownership is - not transferred. + @param msg The message to write. + The object must remain valid at least until the + handler is called; ownership is not transferred. @param handler The handler to be called when the operation completes. Copies will be made of the handler as required. @@ -236,23 +485,21 @@ write(SyncWriteStream& stream, 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`. + + @see @ref message */ template -#if GENERATING_DOCS -void_or_deduced -#else -typename async_completion< - WriteHandler, void(error_code)>::result_type -#endif +async_return_type< + WriteHandler, void(error_code)> async_write(AsyncWriteStream& stream, - message const& msg, + message& msg, WriteHandler&& handler); //------------------------------------------------------------------------------ -/** Serialize a HTTP/1 header to a `std::ostream`. +/** Serialize an HTTP/1 header to a `std::ostream`. The function converts the header to its HTTP/1 serialized representation and stores the result in the output stream. @@ -266,7 +513,7 @@ std::ostream& operator<<(std::ostream& os, header const& msg); -/** Serialize a HTTP/1 message to a `std::ostream`. +/** Serialize an HTTP/1 message to a `std::ostream`. The function converts the message to its HTTP/1 serialized representation and stores the result in the output stream. diff --git a/include/beast/version.hpp b/include/beast/version.hpp index 2ba20a08a2..d827f89f3b 100644 --- a/include/beast/version.hpp +++ b/include/beast/version.hpp @@ -9,15 +9,18 @@ #define BEAST_VERSION_HPP #include +#include -// follows http://semver.org +/** @def BEAST_API_VERSION -// BEAST_VERSION % 100 is the patch level -// BEAST_VERSION / 100 % 1000 is the minor version -// BEAST_VERSION / 100000 is the major version -// -#define BEAST_VERSION 100000 + Identifies the API version of Beast. -#define BEAST_VERSION_STRING "1.0.0-b34" + This is a simple integer that is incremented by one every time + a set of code changes is merged to the master or develop branch. +*/ +#define BEAST_VERSION 79 + +#define BEAST_VERSION_STRING "Beast/" BOOST_STRINGIZE(BEAST_VERSION) #endif + diff --git a/include/beast/websocket/detail/debug.hpp b/include/beast/websocket/detail/debug.hpp deleted file mode 100644 index 2437993eb0..0000000000 --- a/include/beast/websocket/detail/debug.hpp +++ /dev/null @@ -1,75 +0,0 @@ -// -// 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_WEBSOCKET_DETAIL_DEBUG_HPP -#define BEAST_WEBSOCKET_DETAIL_DEBUG_HPP - -#include -#include -#include -#include - -namespace beast { -namespace websocket { -namespace detail { - -template -std::string -to_hex(boost::asio::const_buffer b) -{ - using namespace boost::asio; - std::stringstream ss; - auto p = buffer_cast(b); - auto n = buffer_size(b); - while(n--) - { - ss << - std::setfill('0') << - std::setw(2) << - std::hex << int(*p++) << " "; - } - return ss.str(); -} - -template -std::string -to_hex(Buffers const& bs) -{ - std::string s; - for(auto const& b : bs) - s.append(to_hex(boost::asio::const_buffer(b))); - return s; -} - -template -std::string -buffers_to_string(Buffers const& bs) -{ - using namespace boost::asio; - std::string s; - s.reserve(buffer_size(bs)); - for(auto const& b : bs) - s.append(buffer_cast(b), - buffer_size(b)); - return s; -} - -template -std::string -format(std::string s) -{ - auto const w = 84; - for(int n = w*(s.size()/w); n>0; n-=w) - s.insert(n, 1, '\n'); - return s; -} - -} // detail -} // websocket -} // beast - -#endif diff --git a/include/beast/websocket/detail/decorator.hpp b/include/beast/websocket/detail/decorator.hpp deleted file mode 100644 index 558ff9e354..0000000000 --- a/include/beast/websocket/detail/decorator.hpp +++ /dev/null @@ -1,167 +0,0 @@ -// -// 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_WEBSOCKET_DETAIL_DECORATOR_HPP -#define BEAST_WEBSOCKET_DETAIL_DECORATOR_HPP - -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace websocket { -namespace detail { - -using request_type = http::request; - -using response_type = http::response; - -struct abstract_decorator -{ - virtual - ~abstract_decorator() = default; - - virtual - void - operator()(request_type& req) const = 0; - - virtual - void - operator()(response_type& res) const = 0; -}; - -template -class decorator : public abstract_decorator -{ - F f_; - - class call_req_possible - { - template().operator()( - std::declval()), - std::true_type{})> - static R check(int); - template - static std::false_type check(...); - public: - using type = decltype(check(0)); - }; - - class call_res_possible - { - template().operator()( - std::declval()), - std::true_type{})> - static R check(int); - template - static std::false_type check(...); - public: - using type = decltype(check(0)); - }; - -public: - decorator(F&& t) - : f_(std::move(t)) - { - } - - decorator(F const& t) - : f_(t) - { - } - - void - operator()(request_type& req) const override - { - (*this)(req, typename call_req_possible::type{}); - } - - void - operator()(response_type& res) const override - { - (*this)(res, typename call_res_possible::type{}); - } - -private: - void - operator()(request_type& req, std::true_type) const - { - f_(req); - } - - void - operator()(request_type& req, std::false_type) const - { - req.fields.replace("User-Agent", - std::string{"Beast/"} + BEAST_VERSION_STRING); - } - - void - operator()(response_type& res, std::true_type) const - { - f_(res); - } - - void - operator()(response_type& res, std::false_type) const - { - res.fields.replace("Server", - std::string{"Beast/"} + BEAST_VERSION_STRING); - } -}; - -class decorator_type -{ - std::shared_ptr p_; - -public: - decorator_type() = delete; - decorator_type(decorator_type&&) = default; - decorator_type(decorator_type const&) = default; - decorator_type& operator=(decorator_type&&) = default; - decorator_type& operator=(decorator_type const&) = default; - - template::type, - decorator_type>::value>> - decorator_type(F&& f) - : p_(std::make_shared>( - std::forward(f))) - { - BOOST_ASSERT(p_); - } - - void - operator()(request_type& req) - { - (*p_)(req); - BOOST_ASSERT(p_); - } - - void - operator()(response_type& res) - { - (*p_)(res); - BOOST_ASSERT(p_); - } -}; - -struct default_decorator -{ -}; - -} // detail -} // websocket -} // beast - -#endif diff --git a/include/beast/websocket/detail/endian.hpp b/include/beast/websocket/detail/endian.hpp deleted file mode 100644 index e9276089e9..0000000000 --- a/include/beast/websocket/detail/endian.hpp +++ /dev/null @@ -1,71 +0,0 @@ -// -// 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_WEBSOCKET_DETAIL_ENDIAN_HPP -#define BEAST_WEBSOCKET_DETAIL_ENDIAN_HPP - -#include - -namespace beast { -namespace websocket { -namespace detail { - -inline -std::uint16_t -big_uint16_to_native(void const* buf) -{ - auto const p = reinterpret_cast< - std::uint8_t const*>(buf); - return (p[0]<<8) + p[1]; -} - -inline -std::uint64_t -big_uint64_to_native(void const* buf) -{ - auto const p = reinterpret_cast< - std::uint8_t const*>(buf); - return - (static_cast(p[0])<<56) + - (static_cast(p[1])<<48) + - (static_cast(p[2])<<40) + - (static_cast(p[3])<<32) + - (static_cast(p[4])<<24) + - (static_cast(p[5])<<16) + - (static_cast(p[6])<< 8) + - p[7]; -} - -inline -std::uint32_t -little_uint32_to_native(void const* buf) -{ - auto const p = reinterpret_cast< - std::uint8_t const*>(buf); - return - p[0] + - (static_cast(p[1])<< 8) + - (static_cast(p[2])<<16) + - (static_cast(p[3])<<24); -} - -inline -void -native_to_little_uint32(std::uint32_t v, void* buf) -{ - auto p = reinterpret_cast(buf); - p[0] = v & 0xff; - p[1] = (v >> 8) & 0xff; - p[2] = (v >> 16) & 0xff; - p[3] = (v >> 24) & 0xff; -} - -} // detail -} // websocket -} // beast - -#endif diff --git a/include/beast/websocket/detail/frame.hpp b/include/beast/websocket/detail/frame.hpp index c3def4f3af..512f817a79 100644 --- a/include/beast/websocket/detail/frame.hpp +++ b/include/beast/websocket/detail/frame.hpp @@ -9,10 +9,9 @@ #define BEAST_WEBSOCKET_DETAIL_FRAME_HPP #include -#include #include #include -#include +#include #include #include #include @@ -23,6 +22,77 @@ namespace beast { namespace websocket { namespace detail { +inline +std::uint16_t +big_uint16_to_native(void const* buf) +{ + auto const p = reinterpret_cast< + std::uint8_t const*>(buf); + return (p[0]<<8) + p[1]; +} + +inline +std::uint64_t +big_uint64_to_native(void const* buf) +{ + auto const p = reinterpret_cast< + std::uint8_t const*>(buf); + return + (static_cast(p[0])<<56) + + (static_cast(p[1])<<48) + + (static_cast(p[2])<<40) + + (static_cast(p[3])<<32) + + (static_cast(p[4])<<24) + + (static_cast(p[5])<<16) + + (static_cast(p[6])<< 8) + + p[7]; +} + +inline +std::uint32_t +little_uint32_to_native(void const* buf) +{ + auto const p = reinterpret_cast< + std::uint8_t const*>(buf); + return + p[0] + + (static_cast(p[1])<< 8) + + (static_cast(p[2])<<16) + + (static_cast(p[3])<<24); +} + +inline +void +native_to_little_uint32(std::uint32_t v, void* buf) +{ + auto p = reinterpret_cast(buf); + p[0] = v & 0xff; + p[1] = (v >> 8) & 0xff; + p[2] = (v >> 16) & 0xff; + p[3] = (v >> 24) & 0xff; +} + +/** WebSocket frame header opcodes. */ +enum class opcode : std::uint8_t +{ + cont = 0, + text = 1, + binary = 2, + rsv3 = 3, + rsv4 = 4, + rsv5 = 5, + rsv6 = 6, + rsv7 = 7, + close = 8, + ping = 9, + pong = 10, + crsvb = 11, + crsvc = 12, + crsvd = 13, + crsve = 14, + crsvf = 15 +}; + // Contents of a WebSocket frame header struct frame_header { @@ -38,11 +108,11 @@ struct frame_header // holds the largest possible frame header using fh_streambuf = - static_streambuf_n<14>; + static_buffer_n<14>; // holds the largest possible control frame using frame_streambuf = - static_streambuf_n< 2 + 8 + 4 + 125 >; + static_buffer_n< 2 + 8 + 4 + 125 >; inline bool constexpr @@ -67,33 +137,31 @@ is_control(opcode op) return op >= opcode::close; } -// Returns `true` if a close code is valid inline bool -is_valid(close_code::value code) +is_valid_close_code(std::uint16_t v) { - auto const v = code; switch(v) { - case 1000: - case 1001: - case 1002: - case 1003: - case 1007: - case 1008: - case 1009: - case 1010: - case 1011: - case 1012: - case 1013: + case close_code::normal: // 1000 + case close_code::going_away: // 1001 + case close_code::protocol_error: // 1002 + case close_code::unknown_data: // 1003 + case close_code::bad_payload: // 1007 + case close_code::policy_error: // 1008 + case close_code::too_big: // 1009 + case close_code::needs_extension: // 1010 + case close_code::internal_error: // 1011 + case close_code::service_restart: // 1012 + case close_code::try_again_later: // 1013 return true; // explicitly reserved - case 1004: - case 1005: - case 1006: - case 1014: - case 1015: + case close_code::reserved1: // 1004 + case close_code::no_status: // 1005 + case close_code::abnormal: // 1006 + case close_code::reserved2: // 1014 + case close_code::reserved3: // 1015 return false; } // reserved @@ -175,7 +243,7 @@ read(ping_data& data, Buffers const& bs) template void read(close_reason& cr, - Buffers const& bs, close_code::value& code) + Buffers const& bs, close_code& code) { using boost::asio::buffer; using boost::asio::buffer_copy; @@ -201,7 +269,7 @@ read(close_reason& cr, cr.code = big_uint16_to_native(&b[0]); cb.consume(2); n -= 2; - if(! is_valid(cr.code)) + if(! is_valid_close_code(cr.code)) { code = close_code::protocol_error; return; diff --git a/include/beast/websocket/detail/hybi13.hpp b/include/beast/websocket/detail/hybi13.hpp index ec09918b1a..ccfd8854f5 100644 --- a/include/beast/websocket/detail/hybi13.hpp +++ b/include/beast/websocket/detail/hybi13.hpp @@ -8,9 +8,11 @@ #ifndef BEAST_WEBSOCKET_DETAIL_HYBI13_HPP #define BEAST_WEBSOCKET_DETAIL_HYBI13_HPP +#include +#include #include #include -#include +#include #include #include #include @@ -20,11 +22,17 @@ namespace beast { namespace websocket { namespace detail { +using sec_ws_key_type = static_string< + beast::detail::base64::encoded_size(16)>; + +using sec_ws_accept_type = static_string< + beast::detail::base64::encoded_size(20)>; + template -std::string -make_sec_ws_key(Gen& g) +void +make_sec_ws_key(sec_ws_key_type& key, Gen& g) { - std::array a; + char a[16]; for(int i = 0; i < 16; i += 4) { auto const v = g(); @@ -33,24 +41,27 @@ make_sec_ws_key(Gen& g) a[i+2] = (v >> 16) & 0xff; a[i+3] = (v >> 24) & 0xff; } - return beast::detail::base64_encode( - a.data(), a.size()); + key.resize(key.max_size()); + key.resize(beast::detail::base64::encode( + key.data(), &a[0], 16)); } template -std::string -make_sec_ws_accept(boost::string_ref const& key) +void +make_sec_ws_accept(sec_ws_accept_type& accept, + string_view key) { - std::string s(key.data(), key.size()); - s += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + BOOST_ASSERT(key.size() <= sec_ws_key_type::max_size_n); + static_string m(key); + m.append("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); beast::detail::sha1_context ctx; beast::detail::init(ctx); - beast::detail::update(ctx, s.data(), s.size()); - std::array digest; - beast::detail::finish(ctx, digest.data()); - return beast::detail::base64_encode( - digest.data(), digest.size()); + beast::detail::update(ctx, m.data(), m.size()); + char digest[beast::detail::sha1_context::digest_size]; + beast::detail::finish(ctx, &digest[0]); + accept.resize(accept.max_size()); + accept.resize(beast::detail::base64::encode( + accept.data(), &digest[0], sizeof(digest))); } } // detail diff --git a/include/beast/websocket/detail/mask.hpp b/include/beast/websocket/detail/mask.hpp index fdb8e86f49..27a3a155a2 100644 --- a/include/beast/websocket/detail/mask.hpp +++ b/include/beast/websocket/detail/mask.hpp @@ -254,7 +254,7 @@ void mask_inplace( MutableBuffers const& bs, KeyType& key) { - for(auto const& b : bs) + for(boost::asio::mutable_buffer b : bs) mask_inplace(b, key); } diff --git a/include/beast/websocket/detail/invokable.hpp b/include/beast/websocket/detail/pausation.hpp similarity index 71% rename from include/beast/websocket/detail/invokable.hpp rename to include/beast/websocket/detail/pausation.hpp index 8070108321..53a99884a3 100644 --- a/include/beast/websocket/detail/invokable.hpp +++ b/include/beast/websocket/detail/pausation.hpp @@ -5,8 +5,8 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_WEBSOCKET_DETAIL_INVOKABLE_HPP -#define BEAST_WEBSOCKET_DETAIL_INVOKABLE_HPP +#ifndef BEAST_WEBSOCKET_DETAIL_PAUSATION_HPP +#define BEAST_WEBSOCKET_DETAIL_PAUSATION_HPP #include #include @@ -19,16 +19,18 @@ namespace beast { namespace websocket { namespace detail { -// "Parks" a composed operation, to invoke later +// A container that holds a suspended, asynchronous composed +// operation. The contained object may be invoked later to +// resume the operation, or the container may be destroyed. // -class invokable +class pausation { struct base { base() = default; base(base &&) = default; virtual ~base() = default; - virtual void move(void* p) = 0; + virtual base* move(void* p) = 0; virtual void operator()() = 0; }; @@ -46,10 +48,10 @@ class invokable { } - void + base* move(void* p) override { - ::new(p) holder(std::move(*this)); + return ::new(p) holder(std::move(*this)); } void @@ -58,7 +60,7 @@ class invokable F f_(std::move(f)); this->~holder(); // invocation of f_() can - // assign a new invokable. + // assign a new object to *this. f_(); } }; @@ -86,38 +88,34 @@ class invokable alignas(holder) buf_type buf_; public: - ~invokable() + ~pausation() { if(base_) base_->~base(); } - invokable() = default; + pausation() = default; - invokable(invokable&& other) + pausation(pausation&& other) { if(other.base_) { - // type-pun - base_ = reinterpret_cast(&buf_[0]); - other.base_->move(buf_); + base_ = other.base_->move(buf_); other.base_ = nullptr; } } - invokable& - operator=(invokable&& other) + pausation& + operator=(pausation&& other) { - // Engaged invokables must be invoked before + // Engaged pausations must be invoked before // assignment otherwise the io_service // completion invariants are broken. BOOST_ASSERT(! base_); if(other.base_) { - // type-pun - base_ = reinterpret_cast(&buf_[0]); - other.base_->move(buf_); + base_ = other.base_->move(buf_); other.base_ = nullptr; } return *this; @@ -143,14 +141,13 @@ public: template void -invokable::emplace(F&& f) +pausation::emplace(F&& f) { - static_assert(sizeof(buf_type) >= sizeof(holder), + using type = holder::type>; + static_assert(sizeof(buf_type) >= sizeof(type), "buffer too small"); BOOST_ASSERT(! base_); - ::new(buf_) holder(std::forward(f)); - // type-pun - base_ = reinterpret_cast(&buf_[0]); + base_ = ::new(buf_) type{std::forward(f)}; } } // detail diff --git a/include/beast/websocket/detail/pmd_extension.hpp b/include/beast/websocket/detail/pmd_extension.hpp index e7c087a3d9..ac1a5a91cf 100644 --- a/include/beast/websocket/detail/pmd_extension.hpp +++ b/include/beast/websocket/detail/pmd_extension.hpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include #include #include @@ -46,7 +46,7 @@ struct pmd_offer template int -parse_bits(boost::string_ref const& s) +parse_bits(string_view s) { if(s.size() == 0) return -1; @@ -66,9 +66,10 @@ parse_bits(boost::string_ref const& s) // Parse permessage-deflate request fields // -template +template void -pmd_read(pmd_offer& offer, Fields const& fields) +pmd_read(pmd_offer& offer, + http::basic_fields const& fields) { offer.accept = false; offer.server_max_window_bits= 0; @@ -76,16 +77,15 @@ pmd_read(pmd_offer& offer, Fields const& fields) offer.server_no_context_takeover = false; offer.client_no_context_takeover = false; - using beast::detail::ci_equal; http::ext_list list{ fields["Sec-WebSocket-Extensions"]}; for(auto const& ext : list) { - if(ci_equal(ext.first, "permessage-deflate")) + if(iequals(ext.first, "permessage-deflate")) { for(auto const& param : ext.second) { - if(ci_equal(param.first, + if(iequals(param.first, "server_max_window_bits")) { if(offer.server_max_window_bits != 0) @@ -113,7 +113,7 @@ pmd_read(pmd_offer& offer, Fields const& fields) return; // MUST decline } } - else if(ci_equal(param.first, + else if(iequals(param.first, "client_max_window_bits")) { if(offer.client_max_window_bits != 0) @@ -141,7 +141,7 @@ pmd_read(pmd_offer& offer, Fields const& fields) offer.client_max_window_bits = -1; } } - else if(ci_equal(param.first, + else if(iequals(param.first, "server_no_context_takeover")) { if(offer.server_no_context_takeover) @@ -160,7 +160,7 @@ pmd_read(pmd_offer& offer, Fields const& fields) } offer.server_no_context_takeover = true; } - else if(ci_equal(param.first, + else if(iequals(param.first, "client_no_context_takeover")) { if(offer.client_no_context_takeover) @@ -195,18 +195,19 @@ pmd_read(pmd_offer& offer, Fields const& fields) // Set permessage-deflate fields for a client offer // -template +template void -pmd_write(Fields& fields, pmd_offer const& offer) +pmd_write(http::basic_fields& fields, + pmd_offer const& offer) { - std::string s; + static_string<512> s; s = "permessage-deflate"; if(offer.server_max_window_bits != 0) { if(offer.server_max_window_bits != -1) { s += "; server_max_window_bits="; - s += std::to_string( + s += to_static_string( offer.server_max_window_bits); } else @@ -219,7 +220,7 @@ pmd_write(Fields& fields, pmd_offer const& offer) if(offer.client_max_window_bits != -1) { s += "; client_max_window_bits="; - s += std::to_string( + s += to_static_string( offer.client_max_window_bits); } else @@ -235,15 +236,15 @@ pmd_write(Fields& fields, pmd_offer const& offer) { s += "; client_no_context_takeover"; } - fields.replace("Sec-WebSocket-Extensions", s); + fields.set(http::field::sec_websocket_extensions, s); } // Negotiate a permessage-deflate client offer // -template +template void pmd_negotiate( - Fields& fields, + http::basic_fields& fields, pmd_offer& config, pmd_offer const& offer, permessage_deflate const& o) @@ -255,7 +256,7 @@ pmd_negotiate( } config.accept = true; - std::string s = "permessage-deflate"; + static_string<512> s = "permessage-deflate"; config.server_no_context_takeover = offer.server_no_context_takeover || @@ -285,7 +286,7 @@ pmd_negotiate( config.server_max_window_bits = 9; s += "; server_max_window_bits="; - s += std::to_string( + s += to_static_string( config.server_max_window_bits); } @@ -298,7 +299,7 @@ pmd_negotiate( if(config.client_max_window_bits < 15) { s += "; client_max_window_bits="; - s += std::to_string( + s += to_static_string( config.client_max_window_bits); } break; @@ -323,12 +324,12 @@ pmd_negotiate( o.client_max_window_bits, offer.client_max_window_bits); s += "; client_max_window_bits="; - s += std::to_string( + s += to_static_string( config.client_max_window_bits); break; } if(config.accept) - fields.replace("Sec-WebSocket-Extensions", s); + fields.set(http::field::sec_websocket_extensions, s); } // Normalize the server's response @@ -356,7 +357,7 @@ template void inflate( InflateStream& zi, - DynamicBuffer& dynabuf, + DynamicBuffer& buffer, boost::asio::const_buffer const& in, error_code& ec) { @@ -368,18 +369,18 @@ inflate( for(;;) { // VFALCO we could be smarter about the size - auto const bs = dynabuf.prepare( - read_size_helper(dynabuf, 65536)); + auto const bs = buffer.prepare( + read_size_or_throw(buffer, 65536)); auto const out = *bs.begin(); zs.avail_out = buffer_size(out); zs.next_out = buffer_cast(out); zi.write(zs, zlib::Flush::sync, ec); - dynabuf.commit(zs.total_out); + buffer.commit(zs.total_out); zs.total_out = 0; if( ec == zlib::error::need_buffers || ec == zlib::error::end_of_stream) { - ec = {}; + ec.assign(0, ec.category()); break; } if(ec) @@ -408,7 +409,7 @@ deflate( zs.next_in = nullptr; zs.avail_out = buffer_size(out); zs.next_out = buffer_cast(out); - for(auto const& in : cb) + for(boost::asio::const_buffer in : cb) { zs.avail_in = buffer_size(in); if(zs.avail_in == 0) @@ -421,7 +422,7 @@ deflate( return false; BOOST_ASSERT(zs.avail_out == 0); BOOST_ASSERT(zs.total_out == buffer_size(out)); - ec = {}; + ec.assign(0, ec.category()); break; } if(zs.avail_out == 0) @@ -445,7 +446,7 @@ deflate( zo.write(zs, zlib::Flush::block, ec); BOOST_ASSERT(! ec || ec == zlib::error::need_buffers); if(ec == zlib::error::need_buffers) - ec = {}; + ec.assign(0, ec.category()); if(ec) return false; if(zs.avail_out >= 6) @@ -460,6 +461,7 @@ deflate( } } } + ec.assign(0, ec.category()); out = buffer( buffer_cast(out), zs.total_out); return true; diff --git a/include/beast/websocket/detail/stream_base.hpp b/include/beast/websocket/detail/stream_base.hpp deleted file mode 100644 index 6cd6731fba..0000000000 --- a/include/beast/websocket/detail/stream_base.hpp +++ /dev/null @@ -1,568 +0,0 @@ -// -// 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_WEBSOCKET_DETAIL_STREAM_BASE_HPP -#define BEAST_WEBSOCKET_DETAIL_STREAM_BASE_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace websocket { -namespace detail { - -/// Identifies the role of a WebSockets stream. -enum class role_type -{ - /// Stream is operating as a client. - client, - - /// Stream is operating as a server. - server -}; - -//------------------------------------------------------------------------------ - -struct stream_base -{ -protected: - friend class frame_test; - - struct op {}; - - detail::maskgen maskgen_; // source of mask keys - decorator_type d_; // adorns http messages - bool keep_alive_ = false; // close on failed upgrade - std::size_t rd_msg_max_ = - 16 * 1024 * 1024; // max message size - bool wr_autofrag_ = true; // auto fragment - std::size_t wr_buf_size_ = 4096; // write buffer size - std::size_t rd_buf_size_ = 4096; // read buffer size - opcode wr_opcode_ = opcode::text; // outgoing message type - ping_cb ping_cb_; // ping callback - role_type role_; // server or client - bool failed_; // the connection failed - - bool wr_close_; // sent close frame - op* wr_block_; // op currenly writing - - ping_data* ping_data_; // where to put the payload - invokable rd_op_; // read parking - invokable wr_op_; // write parking - invokable ping_op_; // ping parking - close_reason cr_; // set from received close frame - - // State information for the message being received - // - struct rd_t - { - // opcode of current message being read - opcode op; - - // `true` if the next frame is a continuation. - bool cont; - - // Checks that test messages are valid utf8 - detail::utf8_checker utf8; - - // Size of the current message so far. - std::uint64_t size; - - // Size of the read buffer. - // This gets set to the read buffer size option at the - // beginning of sending a message, so that the option can be - // changed mid-send without affecting the current message. - std::size_t buf_size; - - // The read buffer. Used for compression and masking. - std::unique_ptr buf; - }; - - rd_t rd_; - - // State information for the message being sent - // - struct wr_t - { - // `true` if next frame is a continuation, - // `false` if next frame starts a new message - bool cont; - - // `true` if this message should be auto-fragmented - // This gets set to the auto-fragment option at the beginning - // of sending a message, so that the option can be changed - // mid-send without affecting the current message. - bool autofrag; - - // `true` if this message should be compressed. - // This gets set to the compress option at the beginning of - // of sending a message, so that the option can be changed - // mid-send without affecting the current message. - bool compress; - - // Size of the write buffer. - // This gets set to the write buffer size option at the - // beginning of sending a message, so that the option can be - // changed mid-send without affecting the current message. - std::size_t buf_size; - - // The write buffer. Used for compression and masking. - // The buffer is allocated or reallocated at the beginning of - // sending a message. - std::unique_ptr buf; - }; - - wr_t wr_; - - // State information for the permessage-deflate extension - struct pmd_t - { - // `true` if current read message is compressed - bool rd_set; - - zlib::deflate_stream zo; - zlib::inflate_stream zi; - }; - - // If not engaged, then permessage-deflate is not - // enabled for the currently active session. - std::unique_ptr pmd_; - - // Local options for permessage-deflate - permessage_deflate pmd_opts_; - - // Offer for clients, negotiated result for servers - pmd_offer pmd_config_; - - stream_base(stream_base&&) = default; - stream_base(stream_base const&) = delete; - stream_base& operator=(stream_base&&) = default; - stream_base& operator=(stream_base const&) = delete; - - stream_base() - : d_(detail::default_decorator{}) - { - } - - template - void - open(role_type role); - - template - void - close(); - - template - std::size_t - read_fh1(detail::frame_header& fh, - DynamicBuffer& db, close_code::value& code); - - template - void - read_fh2(detail::frame_header& fh, - DynamicBuffer& db, close_code::value& code); - - // Called before receiving the first frame of each message - template - void - rd_begin(); - - // Called before sending the first frame of each message - // - template - void - wr_begin(); - - template - void - write_close(DynamicBuffer& db, close_reason const& rc); - - template - void - write_ping(DynamicBuffer& db, opcode op, ping_data const& data); -}; - -template -void -stream_base:: -open(role_type role) -{ - // VFALCO TODO analyze and remove dupe code in reset() - role_ = role; - failed_ = false; - rd_.cont = false; - wr_close_ = false; - wr_block_ = nullptr; // should be nullptr on close anyway - ping_data_ = nullptr; // should be nullptr on close anyway - - wr_.cont = false; - wr_.buf_size = 0; - - if(((role_ == role_type::client && pmd_opts_.client_enable) || - (role_ == role_type::server && pmd_opts_.server_enable)) && - pmd_config_.accept) - { - pmd_normalize(pmd_config_); - pmd_.reset(new pmd_t); - if(role_ == role_type::client) - { - pmd_->zi.reset( - pmd_config_.server_max_window_bits); - pmd_->zo.reset( - pmd_opts_.compLevel, - pmd_config_.client_max_window_bits, - pmd_opts_.memLevel, - zlib::Strategy::normal); - } - else - { - pmd_->zi.reset( - pmd_config_.client_max_window_bits); - pmd_->zo.reset( - pmd_opts_.compLevel, - pmd_config_.server_max_window_bits, - pmd_opts_.memLevel, - zlib::Strategy::normal); - } - } -} - -template -void -stream_base:: -close() -{ - rd_.buf.reset(); - wr_.buf.reset(); - pmd_.reset(); -} - -// Read fixed frame header from buffer -// Requires at least 2 bytes -// -template -std::size_t -stream_base:: -read_fh1(detail::frame_header& fh, - DynamicBuffer& db, close_code::value& code) -{ - using boost::asio::buffer; - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - auto const err = - [&](close_code::value cv) - { - code = cv; - return 0; - }; - std::uint8_t b[2]; - BOOST_ASSERT(buffer_size(db.data()) >= sizeof(b)); - db.consume(buffer_copy(buffer(b), db.data())); - std::size_t need; - fh.len = b[1] & 0x7f; - switch(fh.len) - { - case 126: need = 2; break; - case 127: need = 8; break; - default: - need = 0; - } - fh.mask = (b[1] & 0x80) != 0; - if(fh.mask) - need += 4; - fh.op = static_cast(b[0] & 0x0f); - fh.fin = (b[0] & 0x80) != 0; - fh.rsv1 = (b[0] & 0x40) != 0; - fh.rsv2 = (b[0] & 0x20) != 0; - fh.rsv3 = (b[0] & 0x10) != 0; - switch(fh.op) - { - case opcode::binary: - case opcode::text: - if(rd_.cont) - { - // new data frame when continuation expected - return err(close_code::protocol_error); - } - if((fh.rsv1 && ! pmd_) || - fh.rsv2 || fh.rsv3) - { - // reserved bits not cleared - return err(close_code::protocol_error); - } - if(pmd_) - pmd_->rd_set = fh.rsv1; - break; - - case opcode::cont: - if(! rd_.cont) - { - // continuation without an active message - return err(close_code::protocol_error); - } - if(fh.rsv1 || fh.rsv2 || fh.rsv3) - { - // reserved bits not cleared - return err(close_code::protocol_error); - } - break; - - default: - if(is_reserved(fh.op)) - { - // reserved opcode - return err(close_code::protocol_error); - } - if(! fh.fin) - { - // fragmented control message - return err(close_code::protocol_error); - } - if(fh.len > 125) - { - // invalid length for control message - return err(close_code::protocol_error); - } - if(fh.rsv1 || fh.rsv2 || fh.rsv3) - { - // reserved bits not cleared - return err(close_code::protocol_error); - } - break; - } - // unmasked frame from client - if(role_ == role_type::server && ! fh.mask) - { - code = close_code::protocol_error; - return 0; - } - // masked frame from server - if(role_ == role_type::client && fh.mask) - { - code = close_code::protocol_error; - return 0; - } - code = close_code::none; - return need; -} - -// Decode variable frame header from buffer -// -template -void -stream_base:: -read_fh2(detail::frame_header& fh, - DynamicBuffer& db, close_code::value& code) -{ - using boost::asio::buffer; - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - using namespace boost::endian; - switch(fh.len) - { - case 126: - { - std::uint8_t b[2]; - BOOST_ASSERT(buffer_size(db.data()) >= sizeof(b)); - db.consume(buffer_copy(buffer(b), db.data())); - fh.len = big_uint16_to_native(&b[0]); - // length not canonical - if(fh.len < 126) - { - code = close_code::protocol_error; - return; - } - break; - } - case 127: - { - std::uint8_t b[8]; - BOOST_ASSERT(buffer_size(db.data()) >= sizeof(b)); - db.consume(buffer_copy(buffer(b), db.data())); - fh.len = big_uint64_to_native(&b[0]); - // length not canonical - if(fh.len < 65536) - { - code = close_code::protocol_error; - return; - } - break; - } - } - if(fh.mask) - { - std::uint8_t b[4]; - BOOST_ASSERT(buffer_size(db.data()) >= sizeof(b)); - db.consume(buffer_copy(buffer(b), db.data())); - fh.key = little_uint32_to_native(&b[0]); - } - else - { - // initialize this otherwise operator== breaks - fh.key = 0; - } - if(! is_control(fh.op)) - { - if(fh.op != opcode::cont) - { - rd_.size = 0; - rd_.op = fh.op; - } - else - { - if(rd_.size > (std::numeric_limits< - std::uint64_t>::max)() - fh.len) - { - code = close_code::too_big; - return; - } - } - rd_.cont = ! fh.fin; - } - code = close_code::none; -} - -template -void -stream_base:: -rd_begin() -{ - // Maintain the read buffer - if(pmd_) - { - if(! rd_.buf || rd_.buf_size != rd_buf_size_) - { - rd_.buf_size = rd_buf_size_; - rd_.buf.reset(new std::uint8_t[rd_.buf_size]); - } - } -} - -template -void -stream_base:: -wr_begin() -{ - wr_.autofrag = wr_autofrag_; - wr_.compress = static_cast(pmd_); - - // Maintain the write buffer - if( wr_.compress || - role_ == detail::role_type::client) - { - if(! wr_.buf || wr_.buf_size != wr_buf_size_) - { - wr_.buf_size = wr_buf_size_; - wr_.buf.reset(new std::uint8_t[wr_.buf_size]); - } - } - else - { - wr_.buf_size = wr_buf_size_; - wr_.buf.reset(); - } -} - -template -void -stream_base:: -write_close(DynamicBuffer& db, close_reason const& cr) -{ - using namespace boost::endian; - frame_header fh; - fh.op = opcode::close; - fh.fin = true; - fh.rsv1 = false; - fh.rsv2 = false; - fh.rsv3 = false; - fh.len = cr.code == close_code::none ? - 0 : 2 + cr.reason.size(); - fh.mask = role_ == detail::role_type::client; - if(fh.mask) - fh.key = maskgen_(); - detail::write(db, fh); - if(cr.code != close_code::none) - { - detail::prepared_key key; - if(fh.mask) - detail::prepare_key(key, fh.key); - { - std::uint8_t b[2]; - ::new(&b[0]) big_uint16_buf_t{ - (std::uint16_t)cr.code}; - auto d = db.prepare(2); - boost::asio::buffer_copy(d, - boost::asio::buffer(b)); - if(fh.mask) - detail::mask_inplace(d, key); - db.commit(2); - } - if(! cr.reason.empty()) - { - auto d = db.prepare(cr.reason.size()); - boost::asio::buffer_copy(d, - boost::asio::const_buffer( - cr.reason.data(), cr.reason.size())); - if(fh.mask) - detail::mask_inplace(d, key); - db.commit(cr.reason.size()); - } - } -} - -template -void -stream_base:: -write_ping( - DynamicBuffer& db, opcode op, ping_data const& data) -{ - frame_header fh; - fh.op = op; - fh.fin = true; - fh.rsv1 = false; - fh.rsv2 = false; - fh.rsv3 = false; - fh.len = data.size(); - fh.mask = role_ == role_type::client; - if(fh.mask) - fh.key = maskgen_(); - detail::write(db, fh); - if(data.empty()) - return; - detail::prepared_key key; - if(fh.mask) - detail::prepare_key(key, fh.key); - auto d = db.prepare(data.size()); - boost::asio::buffer_copy(d, - boost::asio::const_buffers_1( - data.data(), data.size())); - if(fh.mask) - detail::mask_inplace(d, key); - db.commit(data.size()); -} - -} // detail -} // websocket -} // beast - -#endif diff --git a/include/beast/websocket/detail/type_traits.hpp b/include/beast/websocket/detail/type_traits.hpp new file mode 100644 index 0000000000..7f3481f19d --- /dev/null +++ b/include/beast/websocket/detail/type_traits.hpp @@ -0,0 +1,32 @@ +// +// 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_WEBSOCKET_DETAIL_TYPE_TRAITS_HPP +#define BEAST_WEBSOCKET_DETAIL_TYPE_TRAITS_HPP + +#include +#include + +namespace beast { +namespace websocket { +namespace detail { + +template +using is_RequestDecorator = + typename beast::detail::is_invocable::type; + +template +using is_ResponseDecorator = + typename beast::detail::is_invocable::type; + +} // detail +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/detail/utf8_checker.hpp b/include/beast/websocket/detail/utf8_checker.hpp index d8b1460403..cbd9b70c69 100644 --- a/include/beast/websocket/detail/utf8_checker.hpp +++ b/include/beast/websocket/detail/utf8_checker.hpp @@ -8,9 +8,9 @@ #ifndef BEAST_WEBSOCKET_DETAIL_UTF8_CHECKER_HPP #define BEAST_WEBSOCKET_DETAIL_UTF8_CHECKER_HPP +#include #include #include -#include #include #include @@ -105,7 +105,8 @@ public: template void -utf8_checker_t<_>::reset() +utf8_checker_t<_>:: +reset() { need_ = 0; p_ = have_; @@ -113,7 +114,8 @@ utf8_checker_t<_>::reset() template bool -utf8_checker_t<_>::finish() +utf8_checker_t<_>:: +finish() { auto const success = need_ == 0; reset(); @@ -123,13 +125,14 @@ utf8_checker_t<_>::finish() template template bool -utf8_checker_t<_>::write(ConstBufferSequence const& bs) +utf8_checker_t<_>:: +write(ConstBufferSequence const& bs) { - static_assert(is_ConstBufferSequence::value, + static_assert(is_const_buffer_sequence::value, "ConstBufferSequence requirements not met"); using boost::asio::buffer_cast; using boost::asio::buffer_size; - for(auto const& b : bs) + for(boost::asio::const_buffer b : bs) if(! write(buffer_cast(b), buffer_size(b))) return false; @@ -138,43 +141,44 @@ utf8_checker_t<_>::write(ConstBufferSequence const& bs) template bool -utf8_checker_t<_>::write(std::uint8_t const* in, std::size_t size) +utf8_checker_t<_>:: +write(std::uint8_t const* in, std::size_t size) { auto const valid = - [](std::uint8_t const*& in) + [](std::uint8_t const*& p) { - if (in[0] < 128) + if (p[0] < 128) { - ++in; + ++p; return true; } - if ((in[0] & 0x60) == 0x40) + if ((p[0] & 0x60) == 0x40) { - if ((in[1] & 0xc0) != 0x80) + if ((p[1] & 0xc0) != 0x80) return false; - in += 2; + p += 2; return true; } - if ((in[0] & 0xf0) == 0xe0) + if ((p[0] & 0xf0) == 0xe0) { - if ((in[1] & 0xc0) != 0x80 || - (in[2] & 0xc0) != 0x80 || - (in[0] == 224 && in[1] < 160) || - (in[0] == 237 && in[1] > 159)) + if ((p[1] & 0xc0) != 0x80 || + (p[2] & 0xc0) != 0x80 || + (p[0] == 224 && p[1] < 160) || + (p[0] == 237 && p[1] > 159)) return false; - in += 3; + p += 3; return true; } - if ((in[0] & 0xf8) == 0xf0) + if ((p[0] & 0xf8) == 0xf0) { - if (in[0] > 244 || - (in[1] & 0xc0) != 0x80 || - (in[2] & 0xc0) != 0x80 || - (in[3] & 0xc0) != 0x80 || - (in[0] == 240 && in[1] < 144) || - (in[0] == 244 && in[1] > 143)) + if (p[0] > 244 || + (p[1] & 0xc0) != 0x80 || + (p[2] & 0xc0) != 0x80 || + (p[3] & 0xc0) != 0x80 || + (p[0] == 240 && p[1] < 144) || + (p[0] == 244 && p[1] > 143)) return false; - in += 4; + p += 4; return true; } return false; @@ -195,10 +199,10 @@ utf8_checker_t<_>::write(std::uint8_t const* in, std::size_t size) } if ((have_[0] & 0xf8) == 0xf0) { - auto const size = p_ - have_; - if (size > 2 && (have_[2] & 0xc0) != 0x80) + auto const n = p_ - have_; + if (n > 2 && (have_[2] & 0xc0) != 0x80) return false; - if (size > 1 && + if (n > 1 && ((have_[1] & 0xc0) != 0x80 || (have_[0] == 240 && have_[1] < 144) || (have_[0] == 244 && have_[1] > 143))) @@ -207,17 +211,17 @@ utf8_checker_t<_>::write(std::uint8_t const* in, std::size_t size) return true; }; auto const needed = - [](std::uint8_t const in) + [](std::uint8_t const v) { - if (in < 128) + if (v < 128) return 1; - if (in < 194) + if (v < 194) return 0; - if (in < 224) + if (v < 224) return 2; - if (in < 240) + if (v < 240) return 3; - if (in < 245) + if (v < 245) return 4; return 0; }; @@ -241,39 +245,66 @@ utf8_checker_t<_>::write(std::uint8_t const* in, std::size_t size) p_ = have_; } - auto last = in + size - 7; - while(in < last) - { -#if BEAST_WEBSOCKET_NO_UNALIGNED_READ - auto constexpr align = sizeof(std::size_t) - 1; - auto constexpr mask = static_cast< - std::size_t>(0x8080808080808080 & - ~std::size_t{0}); - if( - ((reinterpret_cast< - std::uintptr_t>(in) & align) == 0) && - (*reinterpret_cast< - std::size_t const*>(in) & mask) == 0) - in += sizeof(std::size_t); - else if(! valid(in)) - return false; -#else - auto constexpr mask = static_cast< - std::size_t>(0x8080808080808080 & - ~std::size_t{0}); - if( - (*reinterpret_cast< - std::size_t const*>(in) & mask) == 0) - in += sizeof(std::size_t); - else if(! valid(in)) - return false; -#endif - } - last += 4; - while(in < last) - if(! valid(in)) - return false; + if(size <= sizeof(std::size_t)) + goto slow; + // align in to sizeof(std::size_t) boundary + { + auto const in0 = in; + auto last = reinterpret_cast( + ((reinterpret_cast(in) + sizeof(std::size_t) - 1) / + sizeof(std::size_t)) * sizeof(std::size_t)); + while(in < last) + { + if(*in & 0x80) + { + size = size - (in - in0); + goto slow; + } + ++in; + } + size = size - (in - in0); + } + + // fast loop + { + auto const in0 = in; + auto last = in + size - 7; + auto constexpr mask = static_cast< + std::size_t>(0x8080808080808080 & ~std::size_t{0}); + while(in < last) + { +#if 0 + std::size_t temp; + std::memcpy(&temp, in, sizeof(temp)); + if((temp & mask) != 0) +#else + // Technically UB but works on all known platforms + if((*reinterpret_cast(in) & mask) != 0) +#endif + { + size = size - (in - in0); + goto slow; + } + in += sizeof(std::size_t); + } + last += 4; + while(in < last) + if(! valid(in)) + return false; + goto tail; + } + + // slow loop: one code point at a time +slow: + { + auto last = in + size - 3; + while(in < last) + if(! valid(in)) + return false; + } + +tail: for(;;) { auto n = end - in; diff --git a/include/beast/websocket/error.hpp b/include/beast/websocket/error.hpp index 8189a1c2ab..e36a37fa06 100644 --- a/include/beast/websocket/error.hpp +++ b/include/beast/websocket/error.hpp @@ -23,32 +23,8 @@ enum class error /// WebSocket connection failed, protocol violation failed, - /// Upgrade request failed, connection is closed - handshake_failed, - - /// Upgrade request failed, but connection is still open - keep_alive, - - /// HTTP response is malformed - response_malformed, - - /// HTTP response failed the upgrade - response_failed, - - /// Upgrade request denied for invalid fields. - response_denied, - - /// Upgrade request is malformed - request_malformed, - - /// Upgrade request fields incorrect - request_invalid, - - /// Upgrade request denied - request_denied, - - /// General WebSocket error - general + /// Upgrade handshake failed + handshake_failed }; } // websocket diff --git a/include/beast/websocket/impl/accept.ipp b/include/beast/websocket/impl/accept.ipp index 877be120c1..892dcffaee 100644 --- a/include/beast/websocket/impl/accept.ipp +++ b/include/beast/websocket/impl/accept.ipp @@ -8,16 +8,20 @@ #ifndef BEAST_WEBSOCKET_IMPL_ACCEPT_IPP #define BEAST_WEBSOCKET_IMPL_ACCEPT_IPP -#include -#include +#include +#include +#include #include #include #include -#include +#include #include -#include #include +#include +#include +#include #include +#include #include #include @@ -35,23 +39,37 @@ class stream::response_op { bool cont; stream& ws; - http::response res; - error_code final_ec; + response_type res; int state = 0; - template - data(Handler&, stream& ws_, - http::request const& req, - bool cont_) + template + data(Handler&, stream& ws_, http::header< + true, http::basic_fields> const& req, + Decorator const& decorator, + bool cont_) : cont(cont_) , ws(ws_) - , res(ws_.build_response(req)) + , res(ws_.build_response(req, decorator)) { - // can't call stream::reset() here - // otherwise accept_op will malfunction - // - if(res.status != 101) - final_ec = error::handshake_failed; + } + + template + data(Handler&, stream& ws_, http::header< + true, http::basic_fields> const& req, + Buffers const& buffers, + Decorator const& decorator, + bool cont_) + : cont(cont_) + , ws(ws_) + , res(ws_.build_response(req, decorator)) + { + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + // VFALCO What about catch(std::length_error const&)? + ws.stream_.buffer().commit(buffer_copy( + ws.stream_.buffer().prepare( + buffer_size(buffers)), buffers)); } }; @@ -77,16 +95,18 @@ public: void* asio_handler_allocate( std::size_t size, response_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + 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, response_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->d_.handler())); } friend @@ -99,8 +119,9 @@ public: friend void asio_handler_invoke(Function&& f, response_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->d_.handler())); } }; @@ -126,12 +147,13 @@ operator()(error_code ec, bool again) // sent response case 1: d.state = 99; - ec = d.final_ec; + if(d.res.result() != + http::status::switching_protocols) + ec = error::handshake_failed; if(! ec) { - pmd_read( - d.ws.pmd_config_, d.res.fields); - d.ws.open(detail::role_type::server); + pmd_read(d.ws.pmd_config_, d.res); + d.ws.open(role_type::server); } break; } @@ -144,26 +166,32 @@ operator()(error_code ec, bool again) // read and respond to an upgrade request // template -template +template class stream::accept_op { struct data { - bool cont; stream& ws; - http::request req; - int state = 0; + Decorator decorator; + http::request_parser p; + + data(Handler&, stream& ws_, + Decorator const& decorator_) + : ws(ws_) + , decorator(decorator_) + { + } template - data(Handler& handler, stream& ws_, - Buffers const& buffers) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , ws(ws_) + data(Handler&, stream& ws_, + Buffers const& buffers, + Decorator const& decorator_) + : ws(ws_) + , decorator(decorator_) { using boost::asio::buffer_copy; using boost::asio::buffer_size; - ws.reset(); + // VFALCO What about catch(std::length_error const&)? ws.stream_.buffer().commit(buffer_copy( ws.stream_.buffer().prepare( buffer_size(buffers)), buffers)); @@ -182,150 +210,122 @@ public: : d_(std::forward(h), ws, std::forward(args)...) { - (*this)(error_code{}, 0, false); } - void operator()(error_code const& ec) - { - (*this)(ec, 0); - } + void operator()(); - void operator()(error_code const& ec, - std::size_t bytes_transferred, bool again = true); + void operator()(error_code ec); friend void* asio_handler_allocate( std::size_t size, accept_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + 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, accept_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->d_.handler())); } friend bool asio_handler_is_continuation(accept_op* op) { - return op->d_->cont; + using boost::asio::asio_handler_is_continuation; + return asio_handler_is_continuation( + std::addressof(op->d_.handler())); } template friend void asio_handler_invoke(Function&& f, accept_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->d_.handler())); } }; template -template +template void -stream::accept_op:: -operator()(error_code const& ec, - std::size_t bytes_transferred, bool again) +stream::accept_op:: +operator()() { - beast::detail::ignore_unused(bytes_transferred); auto& d = *d_; - d.cont = d.cont || again; - while(! ec && d.state != 99) - { - switch(d.state) - { - case 0: - // read message - d.state = 1; - http::async_read(d.ws.next_layer(), - d.ws.stream_.buffer(), d.req, - std::move(*this)); - return; + http::async_read_header(d.ws.next_layer(), + d.ws.stream_.buffer(), d.p, + std::move(*this)); +} - // got message - case 1: - { - // respond to request - auto& ws = d.ws; - auto req = std::move(d.req); - response_op{ - d_.release_handler(), ws, req, true}; - return; - } - } +template +template +void +stream::accept_op:: +operator()(error_code ec) +{ + auto& d = *d_; + if(! ec) + { + BOOST_ASSERT(d.p.is_header_done()); + // Arguments from our state must be + // moved to the stack before releasing + // the handler. + auto& ws = d.ws; + auto const req = d.p.release(); + auto const decorator = d.decorator; + #if 1 + response_op{ + d_.release_handler(), + ws, req, decorator, true}; + #else + // VFALCO This *should* work but breaks + // coroutine invariants in the unit test. + // Also it calls reset() when it shouldn't. + ws.async_accept_ex( + req, decorator, d_.release_handler()); + #endif + return; } d_.invoke(ec); } -template -template -typename async_completion< - AcceptHandler, void(error_code)>::result_type -stream:: -async_accept(AcceptHandler&& handler) -{ - static_assert(is_AsyncStream::value, - "AsyncStream requirements requirements not met"); - return async_accept(boost::asio::null_buffers{}, - std::forward(handler)); -} - -template -template -typename async_completion< - AcceptHandler, void(error_code)>::result_type -stream:: -async_accept(ConstBufferSequence const& bs, AcceptHandler&& handler) -{ - static_assert(is_AsyncStream::value, - "AsyncStream requirements requirements not met"); - static_assert(beast::is_ConstBufferSequence< - ConstBufferSequence>::value, - "ConstBufferSequence requirements not met"); - beast::async_completion< - AcceptHandler, void(error_code) - > completion{handler}; - accept_op{ - completion.handler, *this, bs}; - return completion.result.get(); -} - -template -template -typename async_completion< - AcceptHandler, void(error_code)>::result_type -stream:: -async_accept(http::request const& req, - AcceptHandler&& handler) -{ - static_assert(is_AsyncStream::value, - "AsyncStream requirements requirements not met"); - beast::async_completion< - AcceptHandler, void(error_code) - > completion{handler}; - reset(); - response_op{ - completion.handler, *this, req, - beast_asio_helpers:: - is_continuation(completion.handler)}; - return completion.result.get(); -} +//------------------------------------------------------------------------------ template void stream:: accept() { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); error_code ec; - accept(boost::asio::null_buffers{}, ec); + accept(ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template +template +void +stream:: +accept_ex(ResponseDecorator const& decorator) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + error_code ec; + accept_ex(decorator, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); } template @@ -333,93 +333,477 @@ void stream:: accept(error_code& ec) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); - accept(boost::asio::null_buffers{}, ec); + reset(); + do_accept(&default_decorate_res, ec); +} + +template +template +void +stream:: +accept_ex(ResponseDecorator const& decorator, error_code& ec) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + reset(); + do_accept(decorator, ec); } template template -void +typename std::enable_if::value>::type stream:: accept(ConstBufferSequence const& buffers) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(is_ConstBufferSequence< + static_assert(is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); error_code ec; accept(buffers, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template +template< + class ConstBufferSequence, class ResponseDecorator> +typename std::enable_if::value>::type +stream:: +accept_ex(ConstBufferSequence const& buffers, + ResponseDecorator const &decorator) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + error_code ec; + accept_ex(buffers, decorator, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); } template template -void +typename std::enable_if::value>::type stream:: accept(ConstBufferSequence const& buffers, error_code& ec) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(beast::is_ConstBufferSequence< + static_assert(is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); + reset(); using boost::asio::buffer_copy; using boost::asio::buffer_size; - reset(); stream_.buffer().commit(buffer_copy( stream_.buffer().prepare( buffer_size(buffers)), buffers)); - http::request m; - http::read(next_layer(), stream_.buffer(), m, ec); - if(ec) - return; - accept(m, ec); + do_accept(&default_decorate_res, ec); } template -template +template< + class ConstBufferSequence, class ResponseDecorator> +typename std::enable_if::value>::type +stream:: +accept_ex(ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, error_code& ec) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + reset(); + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + stream_.buffer().commit(buffer_copy( + stream_.buffer().prepare( + buffer_size(buffers)), buffers)); + do_accept(decorator, ec); +} + +template +template void stream:: -accept(http::request const& request) +accept(http::header> const& req) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); error_code ec; - accept(request, ec); + accept(req, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); } template -template +template void stream:: -accept(http::request const& req, - error_code& ec) +accept_ex(http::header> const& req, + ResponseDecorator const& decorator) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + error_code ec; + accept_ex(req, decorator, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template +template +void +stream:: +accept(http::header> const& req, + error_code& ec) +{ + static_assert(is_sync_stream::value, "SyncStream requirements not met"); reset(); - auto const res = build_response(req); - http::write(stream_, res, ec); + do_accept(req, &default_decorate_res, ec); +} + +template +template +void +stream:: +accept_ex(http::header> const& req, + ResponseDecorator const& decorator, error_code& ec) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + reset(); + do_accept(req, decorator, ec); +} + +template +template +void +stream:: +accept(http::header> const& req, + ConstBufferSequence const& buffers) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + error_code ec; + accept(req, buffers, ec); if(ec) - return; - if(res.status != 101) - { - ec = error::handshake_failed; - // VFALCO TODO Respect keep alive setting, perform - // teardown if Connection: close. - return; - } - pmd_read(pmd_config_, req.fields); - open(detail::role_type::server); + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template +template +void +stream:: +accept_ex(http::header> const& req, + ConstBufferSequence const& buffers, + ResponseDecorator const& decorator) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + error_code ec; + accept_ex(req, buffers, decorator, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template +template +void +stream:: +accept(http::header const& req, + ConstBufferSequence const& buffers, error_code& ec) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + reset(); + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + stream_.buffer().commit(buffer_copy( + stream_.buffer().prepare( + buffer_size(buffers)), buffers)); + do_accept(req, &default_decorate_res, ec); +} + +template +template +void +stream:: +accept_ex(http::header> const& req, + ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, + error_code& ec) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + reset(); + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + stream_.buffer().commit(buffer_copy( + stream_.buffer().prepare( + buffer_size(buffers)), buffers)); + do_accept(req, decorator, ec); } //------------------------------------------------------------------------------ +template +template +async_return_type< + AcceptHandler, void(error_code)> +stream:: +async_accept(AcceptHandler&& handler) +{ + static_assert(is_async_stream::value, + "AsyncStream requirements requirements not met"); + async_completion init{handler}; + reset(); + accept_op>{ + init.completion_handler, *this, &default_decorate_res}(); + return init.result.get(); +} + +template +template +async_return_type< + AcceptHandler, void(error_code)> +stream:: +async_accept_ex(ResponseDecorator const& decorator, + AcceptHandler&& handler) +{ + static_assert(is_async_stream::value, + "AsyncStream requirements requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + async_completion init{handler}; + reset(); + accept_op>{ + init.completion_handler, *this, decorator}(); + return init.result.get(); +} + +template +template +typename std::enable_if< + ! http::detail::is_header::value, + async_return_type>::type +stream:: +async_accept(ConstBufferSequence const& buffers, + AcceptHandler&& handler) +{ + static_assert(is_async_stream::value, + "AsyncStream requirements requirements not met"); + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + async_completion init{handler}; + reset(); + accept_op>{ + init.completion_handler, *this, buffers, + &default_decorate_res}(); + return init.result.get(); +} + +template +template +typename std::enable_if< + ! http::detail::is_header::value, + async_return_type>::type +stream:: +async_accept_ex(ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, + AcceptHandler&& handler) +{ + static_assert(is_async_stream::value, + "AsyncStream requirements requirements not met"); + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + async_completion init{handler}; + reset(); + accept_op>{ + init.completion_handler, *this, buffers, + decorator}(); + return init.result.get(); +} + +template +template +async_return_type< + AcceptHandler, void(error_code)> +stream:: +async_accept(http::header> const& req, + AcceptHandler&& handler) +{ + static_assert(is_async_stream::value, + "AsyncStream requirements requirements not met"); + async_completion init{handler}; + reset(); + using boost::asio::asio_handler_is_continuation; + response_op>{init.completion_handler, + *this, req, &default_decorate_res, + asio_handler_is_continuation( + std::addressof(init.completion_handler))}; + return init.result.get(); +} + +template +template +async_return_type< + AcceptHandler, void(error_code)> +stream:: +async_accept_ex(http::header> const& req, + ResponseDecorator const& decorator, AcceptHandler&& handler) +{ + static_assert(is_async_stream::value, + "AsyncStream requirements requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + async_completion init{handler}; + reset(); + using boost::asio::asio_handler_is_continuation; + response_op>{ + init.completion_handler, *this, req, decorator, + asio_handler_is_continuation( + std::addressof(init.completion_handler))}; + return init.result.get(); +} + +template +template +async_return_type< + AcceptHandler, void(error_code)> +stream:: +async_accept(http::header> const& req, + ConstBufferSequence const& buffers, + AcceptHandler&& handler) +{ + static_assert(is_async_stream::value, + "AsyncStream requirements requirements not met"); + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + async_completion init{handler}; + reset(); + using boost::asio::asio_handler_is_continuation; + response_op>{ + init.completion_handler, *this, req, buffers, + &default_decorate_res, asio_handler_is_continuation( + std::addressof(init.completion_handler))}; + return init.result.get(); +} + +template +template +async_return_type< + AcceptHandler, void(error_code)> +stream:: +async_accept_ex(http::header> const& req, + ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, + AcceptHandler&& handler) +{ + static_assert(is_async_stream::value, + "AsyncStream requirements requirements not met"); + static_assert(is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + async_completion init{handler}; + reset(); + using boost::asio::asio_handler_is_continuation; + response_op>{init.completion_handler, + *this, req, buffers, decorator, asio_handler_is_continuation( + std::addressof(init.completion_handler))}; + return init.result.get(); +} + } // websocket } // beast diff --git a/include/beast/websocket/impl/close.ipp b/include/beast/websocket/impl/close.ipp index 983126f924..add4bd6b65 100644 --- a/include/beast/websocket/impl/close.ipp +++ b/include/beast/websocket/impl/close.ipp @@ -8,10 +8,14 @@ #ifndef BEAST_WEBSOCKET_IMPL_CLOSE_IPP #define BEAST_WEBSOCKET_IMPL_CLOSE_IPP -#include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include namespace beast { @@ -25,25 +29,20 @@ template template class stream::close_op { - using fb_type = detail::frame_streambuf; - struct data : op { - bool cont; stream& ws; close_reason cr; - fb_type fb; + detail::frame_streambuf fb; int state = 0; - data(Handler& handler, stream& ws_, + data(Handler&, stream& ws_, close_reason const& cr_) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , ws(ws_) + : ws(ws_) , cr(cr_) { ws.template write_close< - static_streambuf>(fb, cr); + static_buffer>(fb, cr); } }; @@ -59,48 +58,50 @@ public: : d_(std::forward(h), ws, std::forward(args)...) { - (*this)(error_code{}, false); } void operator()() { - (*this)(error_code{}); + (*this)({}); } void - operator()(error_code ec, std::size_t); - - void - operator()(error_code ec, bool again = true); + operator()(error_code ec, + std::size_t bytes_transferred = 0); friend void* asio_handler_allocate( std::size_t size, close_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + 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, close_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->d_.handler())); } friend bool asio_handler_is_continuation(close_op* op) { - return op->d_->cont; + using boost::asio::asio_handler_is_continuation; + return asio_handler_is_continuation( + std::addressof(op->d_.handler())); } template friend void asio_handler_invoke(Function&& f, close_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->d_.handler())); } }; @@ -112,104 +113,90 @@ operator()(error_code ec, std::size_t) { auto& d = *d_; if(ec) - d.ws.failed_ = true; - (*this)(ec); -} - -template -template -void -stream::close_op:: -operator()(error_code ec, bool again) -{ - auto& d = *d_; - d.cont = d.cont || again; - if(ec) - goto upcall; - for(;;) { - switch(d.state) + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.ws.failed_ = true; + goto upcall; + } + switch(d.state) + { + case 0: + if(d.ws.wr_block_) { - case 0: - if(d.ws.wr_block_) - { - // suspend - d.state = 2; - d.ws.wr_op_.template emplace< - close_op>(std::move(*this)); - return; - } - if(d.ws.failed_ || d.ws.wr_close_) - { - // call handler - d.ws.get_io_service().post( - bind_handler(std::move(*this), - boost::asio::error::operation_aborted)); - return; - } - d.ws.wr_block_ = &d; - // [[fallthrough]] - - case 1: - // send close frame - BOOST_ASSERT(d.ws.wr_block_ == &d); - d.state = 99; - d.ws.wr_close_ = true; - boost::asio::async_write(d.ws.stream_, - d.fb.data(), std::move(*this)); - return; - - case 2: - BOOST_ASSERT(! d.ws.wr_block_); - d.ws.wr_block_ = &d; - d.state = 3; - // The current context is safe but might not be - // the same as the one for this operation (since - // we are being called from a write operation). - // Call post to make sure we are invoked the same - // way as the final handler for this operation. - d.ws.get_io_service().post( - bind_handler(std::move(*this), ec)); - return; - - case 3: - BOOST_ASSERT(d.ws.wr_block_ == &d); - if(d.ws.failed_ || d.ws.wr_close_) - { - // call handler - ec = boost::asio::error::operation_aborted; - goto upcall; - } + // suspend d.state = 1; - break; + d.ws.close_op_.emplace(std::move(*this)); + return; + } + d.ws.wr_block_ = &d; + if(d.ws.failed_ || d.ws.wr_close_) + { + // call handler + d.ws.get_io_service().post( + bind_handler(std::move(*this), + boost::asio::error::operation_aborted)); + return; + } - case 99: + do_write: + // send close frame + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.state = 3; + d.ws.wr_close_ = true; + boost::asio::async_write(d.ws.stream_, + d.fb.data(), std::move(*this)); + return; + + case 1: + BOOST_ASSERT(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + d.state = 2; + // The current context is safe but might not be + // the same as the one for this operation (since + // we are being called from a write operation). + // Call post to make sure we are invoked the same + // way as the final handler for this operation. + d.ws.get_io_service().post( + bind_handler(std::move(*this), ec)); + return; + + case 2: + BOOST_ASSERT(d.ws.wr_block_ == &d); + if(d.ws.failed_ || d.ws.wr_close_) + { + // call handler + ec = boost::asio::error::operation_aborted; goto upcall; } + goto do_write; + + case 3: + break; } upcall: - if(d.ws.wr_block_ == &d) - d.ws.wr_block_ = nullptr; + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.ws.wr_block_ = nullptr; d.ws.rd_op_.maybe_invoke() || - d.ws.ping_op_.maybe_invoke(); + d.ws.ping_op_.maybe_invoke() || + d.ws.wr_op_.maybe_invoke(); d_.invoke(ec); } template template -typename async_completion< - CloseHandler, void(error_code)>::result_type +async_return_type< + CloseHandler, void(error_code)> stream:: async_close(close_reason const& cr, CloseHandler&& handler) { - static_assert(is_AsyncStream::value, + static_assert(is_async_stream::value, "AsyncStream requirements not met"); - beast::async_completion< - CloseHandler, void(error_code) - > completion{handler}; - close_op{ - completion.handler, *this, cr}; - return completion.result.get(); + async_completion init{handler}; + close_op>{ + init.completion_handler, *this, cr}({}); + return init.result.get(); } template @@ -217,12 +204,12 @@ void stream:: close(close_reason const& cr) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); error_code ec; close(cr, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); } template @@ -230,14 +217,19 @@ void stream:: close(close_reason const& cr, error_code& ec) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); BOOST_ASSERT(! wr_close_); + if(wr_close_) + { + ec = boost::asio::error::operation_aborted; + return; + } wr_close_ = true; detail::frame_streambuf fb; - write_close(fb, cr); + write_close(fb, cr); boost::asio::write(stream_, fb.data(), ec); - failed_ = ec != 0; + failed_ = !!ec; } //------------------------------------------------------------------------------ diff --git a/include/beast/websocket/impl/error.ipp b/include/beast/websocket/impl/error.ipp index a31d202054..96b0358560 100644 --- a/include/beast/websocket/impl/error.ipp +++ b/include/beast/websocket/impl/error.ipp @@ -28,7 +28,7 @@ public: const char* name() const noexcept override { - return "websocket"; + return "beast.websocket"; } std::string @@ -39,16 +39,9 @@ public: case error::closed: return "WebSocket connection closed normally"; case error::failed: return "WebSocket connection failed due to a protocol violation"; case error::handshake_failed: return "WebSocket Upgrade handshake failed"; - case error::keep_alive: return "WebSocket Upgrade handshake failed but connection is still open"; - case error::response_malformed: return "malformed HTTP response"; - case error::response_failed: return "upgrade request failed"; - case error::response_denied: return "upgrade request denied"; - case error::request_malformed: return "malformed HTTP request"; - case error::request_invalid: return "upgrade request invalid"; - case error::request_denied: return "upgrade request denied"; default: - return "websocket error"; + return "beast.websocket error"; } } diff --git a/include/beast/websocket/impl/handshake.ipp b/include/beast/websocket/impl/handshake.ipp index 11ea56181a..1231b4a852 100644 --- a/include/beast/websocket/impl/handshake.ipp +++ b/include/beast/websocket/impl/handshake.ipp @@ -8,14 +8,18 @@ #ifndef BEAST_WEBSOCKET_IMPL_HANDSHAKE_IPP #define BEAST_WEBSOCKET_IMPL_HANDSHAKE_IPP +#include #include #include #include #include -#include #include -#include +#include +#include +#include +#include #include +#include #include namespace beast { @@ -33,19 +37,25 @@ class stream::handshake_op { bool cont; stream& ws; - std::string key; + response_type* res_p; + detail::sec_ws_key_type key; http::request req; - http::response resp; + response_type res; int state = 0; + template data(Handler& handler, stream& ws_, - boost::string_ref const& host, - boost::string_ref const& resource) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , ws(ws_) - , req(ws.build_request(host, resource, key)) + response_type* res_p_, + string_view host, + string_view target, + Decorator const& decorator) + : ws(ws_) + , res_p(res_p_) + , req(ws.build_request(key, + host, target, decorator)) { + using boost::asio::asio_handler_is_continuation; + cont = asio_handler_is_continuation(std::addressof(handler)); ws.reset(); } }; @@ -72,16 +82,18 @@ public: void* asio_handler_allocate( std::size_t size, handshake_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + 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, handshake_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->d_.handler())); } friend @@ -94,8 +106,9 @@ public: friend void asio_handler_invoke(Function&& f, handshake_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->d_.handler())); } }; @@ -117,10 +130,13 @@ operator()(error_code ec, bool again) d.state = 1; // VFALCO Do we need the ability to move // a message on the async_write? - pmd_read( - d.ws.pmd_config_, d.req.fields); + // + pmd_read(d.ws.pmd_config_, d.req); http::async_write(d.ws.stream_, d.req, std::move(*this)); + // TODO We don't need d.req now. Figure + // out a way to make it a parameter instead + // of a state variable to reduce footprint. return; } @@ -129,78 +145,245 @@ operator()(error_code ec, bool again) // read http response d.state = 2; http::async_read(d.ws.next_layer(), - d.ws.stream_.buffer(), d.resp, + d.ws.stream_.buffer(), d.res, std::move(*this)); return; // got response case 2: { - d.ws.do_response(d.resp, d.key, ec); + d.ws.do_response(d.res, d.key, ec); // call handler d.state = 99; break; } } } + if(d.res_p) + swap(d.res, *d.res_p); d_.invoke(ec); } template template -typename async_completion< - HandshakeHandler, void(error_code)>::result_type +async_return_type< + HandshakeHandler, void(error_code)> stream:: -async_handshake(boost::string_ref const& host, - boost::string_ref const& resource, HandshakeHandler&& handler) +async_handshake(string_view host, + string_view target, + HandshakeHandler&& handler) { - static_assert(is_AsyncStream::value, + static_assert(is_async_stream::value, "AsyncStream requirements not met"); - beast::async_completion< - HandshakeHandler, void(error_code) - > completion{handler}; - handshake_op{ - completion.handler, *this, host, resource}; - return completion.result.get(); + async_completion init{handler}; + handshake_op>{ + init.completion_handler, *this, nullptr, host, + target, &default_decorate_req}; + return init.result.get(); +} + +template +template +async_return_type< + HandshakeHandler, void(error_code)> +stream:: +async_handshake(response_type& res, + string_view host, + string_view target, + HandshakeHandler&& handler) +{ + static_assert(is_async_stream::value, + "AsyncStream requirements not met"); + async_completion init{handler}; + handshake_op>{ + init.completion_handler, *this, &res, host, + target, &default_decorate_req}; + return init.result.get(); +} + +template +template +async_return_type< + HandshakeHandler, void(error_code)> +stream:: +async_handshake_ex(string_view host, + string_view target, + RequestDecorator const& decorator, + HandshakeHandler&& handler) +{ + static_assert(is_async_stream::value, + "AsyncStream requirements not met"); + static_assert(detail::is_RequestDecorator< + RequestDecorator>::value, + "RequestDecorator requirements not met"); + async_completion init{handler}; + handshake_op>{ + init.completion_handler, *this, nullptr, host, + target, decorator}; + return init.result.get(); +} + +template +template +async_return_type< + HandshakeHandler, void(error_code)> +stream:: +async_handshake_ex(response_type& res, + string_view host, + string_view target, + RequestDecorator const& decorator, + HandshakeHandler&& handler) +{ + static_assert(is_async_stream::value, + "AsyncStream requirements not met"); + static_assert(detail::is_RequestDecorator< + RequestDecorator>::value, + "RequestDecorator requirements not met"); + async_completion init{handler}; + handshake_op>{ + init.completion_handler, *this, &res, host, + target, decorator}; + return init.result.get(); } template void stream:: -handshake(boost::string_ref const& host, - boost::string_ref const& resource) +handshake(string_view host, + string_view target) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); error_code ec; - handshake(host, resource, ec); + handshake( + host, target, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); } template void stream:: -handshake(boost::string_ref const& host, - boost::string_ref const& resource, error_code& ec) +handshake(response_type& res, + string_view host, + string_view target) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); - reset(); - std::string key; - { - auto const req = - build_request(host, resource, key); - pmd_read(pmd_config_, req.fields); - http::write(stream_, req, ec); - } + error_code ec; + handshake(res, host, target, ec); if(ec) - return; - http::response res; - http::read(next_layer(), stream_.buffer(), res, ec); + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template +template +void +stream:: +handshake_ex(string_view host, + string_view target, + RequestDecorator const& decorator) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(detail::is_RequestDecorator< + RequestDecorator>::value, + "RequestDecorator requirements not met"); + error_code ec; + handshake_ex(host, target, decorator, ec); if(ec) - return; - do_response(res, key, ec); + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template +template +void +stream:: +handshake_ex(response_type& res, + string_view host, + string_view target, + RequestDecorator const& decorator) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(detail::is_RequestDecorator< + RequestDecorator>::value, + "RequestDecorator requirements not met"); + error_code ec; + handshake_ex(res, host, target, decorator, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template +void +stream:: +handshake(string_view host, + string_view target, error_code& ec) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + do_handshake(nullptr, + host, target, &default_decorate_req, ec); +} + +template +void +stream:: +handshake(response_type& res, + string_view host, + string_view target, + error_code& ec) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + do_handshake(&res, + host, target, &default_decorate_req, ec); +} + +template +template +void +stream:: +handshake_ex(string_view host, + string_view target, + RequestDecorator const& decorator, + error_code& ec) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(detail::is_RequestDecorator< + RequestDecorator>::value, + "RequestDecorator requirements not met"); + do_handshake(nullptr, + host, target, decorator, ec); +} + +template +template +void +stream:: +handshake_ex(response_type& res, + string_view host, + string_view target, + RequestDecorator const& decorator, + error_code& ec) +{ + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + static_assert(detail::is_RequestDecorator< + RequestDecorator>::value, + "RequestDecorator requirements not met"); + do_handshake(&res, + host, target, decorator, ec); } //------------------------------------------------------------------------------ diff --git a/include/beast/websocket/impl/ping.ipp b/include/beast/websocket/impl/ping.ipp index 59071376c9..dc37278e3b 100644 --- a/include/beast/websocket/impl/ping.ipp +++ b/include/beast/websocket/impl/ping.ipp @@ -9,10 +9,14 @@ #define BEAST_WEBSOCKET_IMPL_PING_IPP #include -#include #include -#include +#include +#include #include +#include +#include +#include +#include #include namespace beast { @@ -28,21 +32,18 @@ class stream::ping_op { struct data : op { - bool cont; stream& ws; detail::frame_streambuf fb; int state = 0; - data(Handler& handler, stream& ws_, - opcode op_, ping_data const& payload) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , ws(ws_) + data(Handler&, stream& ws_, + detail::opcode op_, ping_data const& payload) + : ws(ws_) { using boost::asio::buffer; using boost::asio::buffer_copy; ws.template write_ping< - static_streambuf>(fb, op_, payload); + static_buffer>(fb, op_, payload); } }; @@ -58,175 +59,162 @@ public: : d_(std::forward(h), ws, std::forward(args)...) { - (*this)(error_code{}, false); } void operator()() { - (*this)(error_code{}); + (*this)({}); } - void operator()(error_code ec, std::size_t); - - void operator()(error_code ec, bool again = true); + void operator()(error_code ec, + std::size_t bytes_transferred = 0); friend void* asio_handler_allocate( std::size_t size, ping_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + 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, ping_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->d_.handler())); } friend bool asio_handler_is_continuation(ping_op* op) { - return op->d_->cont; + using boost::asio::asio_handler_is_continuation; + return asio_handler_is_continuation( + std::addressof(op->d_.handler())); } template friend void asio_handler_invoke(Function&& f, ping_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->d_.handler())); } }; template template void -stream::ping_op:: +stream:: +ping_op:: operator()(error_code ec, std::size_t) { auto& d = *d_; if(ec) - d.ws.failed_ = true; - (*this)(ec); -} - -template -template -void -stream:: -ping_op:: -operator()(error_code ec, bool again) -{ - auto& d = *d_; - d.cont = d.cont || again; - if(ec) - goto upcall; - for(;;) { - switch(d.state) + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.ws.failed_ = true; + goto upcall; + } + switch(d.state) + { + case 0: + if(d.ws.wr_block_) { - case 0: - if(d.ws.wr_block_) - { - // suspend - d.state = 2; - d.ws.ping_op_.template emplace< - ping_op>(std::move(*this)); - return; - } - if(d.ws.failed_ || d.ws.wr_close_) - { - // call handler - d.state = 99; - d.ws.get_io_service().post( - bind_handler(std::move(*this), - boost::asio::error::operation_aborted)); - return; - } - d.ws.wr_block_ = &d; - // [[fallthrough]] - - case 1: - // send ping frame - BOOST_ASSERT(d.ws.wr_block_ == &d); - d.state = 99; - boost::asio::async_write(d.ws.stream_, - d.fb.data(), std::move(*this)); - return; - - case 2: - BOOST_ASSERT(! d.ws.wr_block_); - d.ws.wr_block_ = &d; - d.state = 3; - // The current context is safe but might not be - // the same as the one for this operation (since - // we are being called from a write operation). - // Call post to make sure we are invoked the same - // way as the final handler for this operation. - d.ws.get_io_service().post( - bind_handler(std::move(*this), ec)); - return; - - case 3: - BOOST_ASSERT(d.ws.wr_block_ == &d); - if(d.ws.failed_ || d.ws.wr_close_) - { - // call handler - ec = boost::asio::error::operation_aborted; - goto upcall; - } + // suspend d.state = 1; - break; + d.ws.ping_op_.emplace(std::move(*this)); + return; + } + d.ws.wr_block_ = &d; + if(d.ws.failed_ || d.ws.wr_close_) + { + // call handler + return d.ws.get_io_service().post( + bind_handler(std::move(*this), + boost::asio::error::operation_aborted)); + } - case 99: + do_write: + // send ping frame + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.state = 3; + boost::asio::async_write(d.ws.stream_, + d.fb.data(), std::move(*this)); + return; + + case 1: + BOOST_ASSERT(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + d.state = 2; + // The current context is safe but might not be + // the same as the one for this operation (since + // we are being called from a write operation). + // Call post to make sure we are invoked the same + // way as the final handler for this operation. + d.ws.get_io_service().post( + bind_handler(std::move(*this), ec)); + return; + + case 2: + BOOST_ASSERT(d.ws.wr_block_ == &d); + if(d.ws.failed_ || d.ws.wr_close_) + { + // call handler + ec = boost::asio::error::operation_aborted; goto upcall; } + goto do_write; + + case 3: + break; } upcall: - if(d.ws.wr_block_ == &d) - d.ws.wr_block_ = nullptr; - d.ws.rd_op_.maybe_invoke() || + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.ws.wr_block_ = nullptr; + d.ws.close_op_.maybe_invoke() || + d.ws.rd_op_.maybe_invoke() || d.ws.wr_op_.maybe_invoke(); d_.invoke(ec); } template template -typename async_completion< - WriteHandler, void(error_code)>::result_type +async_return_type< + WriteHandler, void(error_code)> stream:: async_ping(ping_data const& payload, WriteHandler&& handler) { - static_assert(is_AsyncStream::value, + static_assert(is_async_stream::value, "AsyncStream requirements requirements not met"); - beast::async_completion< - WriteHandler, void(error_code) - > completion{handler}; - ping_op{ - completion.handler, *this, - opcode::ping, payload}; - return completion.result.get(); + async_completion init{handler}; + ping_op>{ + init.completion_handler, *this, + detail::opcode::ping, payload}({}); + return init.result.get(); } template template -typename async_completion< - WriteHandler, void(error_code)>::result_type +async_return_type< + WriteHandler, void(error_code)> stream:: async_pong(ping_data const& payload, WriteHandler&& handler) { - static_assert(is_AsyncStream::value, + static_assert(is_async_stream::value, "AsyncStream requirements requirements not met"); - beast::async_completion< - WriteHandler, void(error_code) - > completion{handler}; - ping_op{ - completion.handler, *this, - opcode::pong, payload}; - return completion.result.get(); + async_completion init{handler}; + ping_op>{ + init.completion_handler, *this, + detail::opcode::pong, payload}({}); + return init.result.get(); } template @@ -237,7 +225,7 @@ ping(ping_data const& payload) error_code ec; ping(payload, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); } template @@ -246,8 +234,8 @@ stream:: ping(ping_data const& payload, error_code& ec) { detail::frame_streambuf db; - write_ping( - db, opcode::ping, payload); + write_ping( + db, detail::opcode::ping, payload); boost::asio::write(stream_, db.data(), ec); } @@ -259,7 +247,7 @@ pong(ping_data const& payload) error_code ec; pong(payload, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); } template @@ -268,8 +256,8 @@ stream:: pong(ping_data const& payload, error_code& ec) { detail::frame_streambuf db; - write_ping( - db, opcode::pong, payload); + write_ping( + db, detail::opcode::pong, payload); boost::asio::write(stream_, db.data(), ec); } diff --git a/include/beast/websocket/impl/read.ipp b/include/beast/websocket/impl/read.ipp index 4defa6743e..ec27dba4f9 100644 --- a/include/beast/websocket/impl/read.ipp +++ b/include/beast/websocket/impl/read.ipp @@ -9,15 +9,19 @@ #define BEAST_WEBSOCKET_IMPL_READ_IPP #include -#include -#include +#include #include -#include -#include -#include +#include +#include #include +#include +#include +#include +#include #include +#include #include +#include #include #include @@ -46,7 +50,6 @@ class stream::read_frame_op { bool cont; stream& ws; - frame_info& fi; DynamicBuffer& db; fb_type fb; std::uint64_t remain; @@ -57,13 +60,12 @@ class stream::read_frame_op int state = 0; data(Handler& handler, stream& ws_, - frame_info& fi_, DynamicBuffer& sb_) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , ws(ws_) - , fi(fi_) + DynamicBuffer& sb_) + : ws(ws_) , db(sb_) { + using boost::asio::asio_handler_is_continuation; + cont = asio_handler_is_continuation(std::addressof(handler)); } }; @@ -79,7 +81,6 @@ public: : d_(std::forward(h), ws, std::forward(args)...) { - (*this)(error_code{}, 0, false); } void operator()() @@ -102,16 +103,18 @@ public: void* asio_handler_allocate( std::size_t size, read_frame_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + 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, read_frame_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->d_.handler())); } friend @@ -124,8 +127,9 @@ public: friend void asio_handler_invoke(Function&& f, read_frame_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->d_.handler())); } }; @@ -160,9 +164,8 @@ operator()(error_code ec, do_control_payload = 8, do_control = 9, do_pong_resume = 10, - do_pong = 12, + do_ponged = 12, do_close_resume = 14, - do_close = 16, do_teardown = 17, do_fail = 19, @@ -170,10 +173,16 @@ operator()(error_code ec, }; auto& d = *d_; + if(d.state == do_teardown + 1 && ec == boost::asio::error::eof) + { + // Rationale: + // http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error + ec.assign(0, ec.category()); + } if(! ec) { d.cont = d.cont || again; - close_code::value code = close_code::none; + close_code code = close_code::none; do { switch(d.state) @@ -210,7 +219,7 @@ operator()(error_code ec, d.remain = d.fh.len; if(d.fh.mask) detail::prepare_key(d.key, d.fh.key); - // fall through + BEAST_FALLTHROUGH; case do_read_payload + 1: d.state = do_read_payload + 2; @@ -223,11 +232,11 @@ operator()(error_code ec, case do_read_payload + 2: { d.remain -= bytes_transferred; - auto const pb = prepare_buffers( + auto const pb = buffer_prefix( bytes_transferred, *d.dmb); if(d.fh.mask) detail::mask_inplace(pb, d.key); - if(d.ws.rd_.op == opcode::text) + if(d.ws.rd_.op == detail::opcode::text) { if(! d.ws.rd_.utf8.write(pb) || (d.remain == 0 && d.fh.fin && @@ -285,7 +294,7 @@ operator()(error_code ec, detail::mask_inplace(in, d.key); auto const prev = d.db.size(); detail::inflate(d.ws.pmd_->zi, d.db, in, ec); - d.ws.failed_ = ec != 0; + d.ws.failed_ = !!ec; if(d.ws.failed_) break; if(d.remain == 0 && d.fh.fin) @@ -295,11 +304,11 @@ operator()(error_code ec, 0x00, 0x00, 0xff, 0xff }; detail::inflate(d.ws.pmd_->zi, d.db, buffer(&empty_block[0], 4), ec); - d.ws.failed_ = ec != 0; + d.ws.failed_ = !!ec; if(d.ws.failed_) break; } - if(d.ws.rd_.op == opcode::text) + if(d.ws.rd_.op == detail::opcode::text) { consuming_bufferszi.reset(); d.state = do_frame_done; @@ -333,9 +342,6 @@ operator()(error_code ec, //------------------------------------------------------------------ case do_frame_done: - // call handler - d.fi.op = d.ws.rd_.op; - d.fi.fin = d.fh.fin; goto upcall; //------------------------------------------------------------------ @@ -395,8 +401,8 @@ operator()(error_code ec, d.state = do_control; break; } - if(d.fh.op == opcode::text || - d.fh.op == opcode::binary) + if(d.fh.op == detail::opcode::text || + d.fh.op == detail::opcode::binary) d.ws.rd_begin(); if(d.fh.len == 0 && ! d.fh.fin) { @@ -425,45 +431,46 @@ operator()(error_code ec, //------------------------------------------------------------------ case do_control: - if(d.fh.op == opcode::ping) + if(d.fh.op == detail::opcode::ping) { ping_data payload; detail::read(payload, d.fb.data()); - d.fb.reset(); - if(d.ws.ping_cb_) - d.ws.ping_cb_(false, payload); + d.fb.consume(d.fb.size()); + if(d.ws.ctrl_cb_) + d.ws.ctrl_cb_( + frame_type::ping, payload); if(d.ws.wr_close_) { // ignore ping when closing d.state = do_read_fh; break; } - d.ws.template write_ping( - d.fb, opcode::pong, payload); + d.ws.template write_ping( + d.fb, detail::opcode::pong, payload); if(d.ws.wr_block_) { // suspend - d.state = do_pong_resume; BOOST_ASSERT(d.ws.wr_block_ != &d); - d.ws.rd_op_.template emplace< - read_frame_op>(std::move(*this)); + d.state = do_pong_resume; + d.ws.rd_op_.emplace(std::move(*this)); return; } - d.state = do_pong; - break; + d.ws.wr_block_ = &d; + goto go_pong; } - else if(d.fh.op == opcode::pong) + if(d.fh.op == detail::opcode::pong) { code = close_code::none; ping_data payload; detail::read(payload, d.fb.data()); - if(d.ws.ping_cb_) - d.ws.ping_cb_(true, payload); - d.fb.reset(); + if(d.ws.ctrl_cb_) + d.ws.ctrl_cb_( + frame_type::pong, payload); + d.fb.consume(d.fb.size()); d.state = do_read_fh; break; } - BOOST_ASSERT(d.fh.op == opcode::close); + BOOST_ASSERT(d.fh.op == detail::opcode::close); { detail::read(d.ws.cr_, d.fb.data(), code); if(code != close_code::none) @@ -472,25 +479,28 @@ operator()(error_code ec, d.state = do_fail; break; } + if(d.ws.ctrl_cb_) + d.ws.ctrl_cb_(frame_type::close, + d.ws.cr_.reason); if(! d.ws.wr_close_) { auto cr = d.ws.cr_; if(cr.code == close_code::none) cr.code = close_code::normal; cr.reason = ""; - d.fb.reset(); + d.fb.consume(d.fb.size()); d.ws.template write_close< - static_streambuf>(d.fb, cr); + static_buffer>(d.fb, cr); if(d.ws.wr_block_) { // suspend + BOOST_ASSERT(d.ws.wr_block_ != &d); d.state = do_close_resume; - d.ws.rd_op_.template emplace< - read_frame_op>(std::move(*this)); + d.ws.rd_op_.emplace(std::move(*this)); return; } - d.state = do_close; - break; + d.ws.wr_block_ = &d; + goto go_close; } d.state = do_teardown; break; @@ -502,48 +512,46 @@ operator()(error_code ec, BOOST_ASSERT(! d.ws.wr_block_); d.ws.wr_block_ = &d; d.state = do_pong_resume + 1; + // The current context is safe but might not be + // the same as the one for this operation (since + // we are being called from a write operation). + // Call post to make sure we are invoked the same + // way as the final handler for this operation. d.ws.get_io_service().post(bind_handler( - std::move(*this), ec, bytes_transferred)); + std::move(*this), ec, 0)); return; case do_pong_resume + 1: + BOOST_ASSERT(d.ws.wr_block_ == &d); if(d.ws.failed_) { // call handler ec = boost::asio::error::operation_aborted; goto upcall; } - // [[fallthrough]] - - //------------------------------------------------------------------ - - case do_pong: if(d.ws.wr_close_) { // ignore ping when closing - if(d.ws.wr_block_) - { - BOOST_ASSERT(d.ws.wr_block_ == &d); - d.ws.wr_block_ = nullptr; - } - d.fb.reset(); + d.ws.wr_block_ = nullptr; + d.fb.consume(d.fb.size()); d.state = do_read_fh; break; } + + //------------------------------------------------------------------ + + go_pong: // send pong - if(! d.ws.wr_block_) - d.ws.wr_block_ = &d; - else - BOOST_ASSERT(d.ws.wr_block_ == &d); - d.state = do_pong + 1; + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.state = do_ponged; boost::asio::async_write(d.ws.stream_, d.fb.data(), std::move(*this)); return; - case do_pong + 1: - d.fb.reset(); - d.state = do_read_fh; + case do_ponged: d.ws.wr_block_ = nullptr; + d.fb.consume(d.fb.size()); + d.state = do_read_fh; break; //------------------------------------------------------------------ @@ -571,20 +579,15 @@ operator()(error_code ec, } if(d.ws.wr_close_) { - // call handler + // already sent a close frame ec = error::closed; goto upcall; } - d.state = do_close; - break; //------------------------------------------------------------------ - case do_close: - if(! d.ws.wr_block_) - d.ws.wr_block_ = &d; - else - BOOST_ASSERT(d.ws.wr_block_ == &d); + go_close: + BOOST_ASSERT(d.ws.wr_block_ == &d); d.state = do_teardown; d.ws.wr_close_ = true; boost::asio::async_write(d.ws.stream_, @@ -612,41 +615,51 @@ operator()(error_code ec, d.state = do_fail + 4; break; } - d.fb.reset(); + d.fb.consume(d.fb.size()); d.ws.template write_close< - static_streambuf>(d.fb, code); + static_buffer>(d.fb, code); if(d.ws.wr_block_) { // suspend + BOOST_ASSERT(d.ws.wr_block_ != &d); d.state = do_fail + 2; - d.ws.rd_op_.template emplace< - read_frame_op>(std::move(*this)); + d.ws.rd_op_.emplace(std::move(*this)); return; } - // fall through + d.ws.wr_block_ = &d; + BEAST_FALLTHROUGH; case do_fail + 1: + BOOST_ASSERT(d.ws.wr_block_ == &d); d.ws.failed_ = true; // send close frame d.state = do_fail + 4; d.ws.wr_close_ = true; - BOOST_ASSERT(! d.ws.wr_block_); - d.ws.wr_block_ = &d; boost::asio::async_write(d.ws.stream_, d.fb.data(), std::move(*this)); return; case do_fail + 2: + // resume + BOOST_ASSERT(! d.ws.wr_block_); + d.ws.wr_block_ = &d; d.state = do_fail + 3; + // The current context is safe but might not be + // the same as the one for this operation (since + // we are being called from a write operation). + // Call post to make sure we are invoked the same + // way as the final handler for this operation. d.ws.get_io_service().post(bind_handler( std::move(*this), ec, bytes_transferred)); return; case do_fail + 3: - if(d.ws.failed_) + BOOST_ASSERT(d.ws.wr_block_ == &d); + if(d.ws.failed_ || d.ws.wr_close_) { - d.state = do_fail + 5; - break; + // call handler + ec = error::failed; + goto upcall; } d.state = do_fail + 1; break; @@ -673,61 +686,65 @@ operator()(error_code ec, upcall: if(d.ws.wr_block_ == &d) d.ws.wr_block_ = nullptr; - d.ws.ping_op_.maybe_invoke() || + d.ws.close_op_.maybe_invoke() || + d.ws.ping_op_.maybe_invoke() || d.ws.wr_op_.maybe_invoke(); - d_.invoke(ec); + bool const fin = (! ec) ? d.fh.fin : false; + d_.invoke(ec, fin); } template template -typename async_completion< - ReadHandler, void(error_code)>::result_type +async_return_type< + ReadHandler, void(error_code, bool)> stream:: -async_read_frame(frame_info& fi, - DynamicBuffer& dynabuf, ReadHandler&& handler) +async_read_frame(DynamicBuffer& buffer, ReadHandler&& handler) { - static_assert(is_AsyncStream::value, + static_assert(is_async_stream::value, "AsyncStream requirements requirements not met"); - static_assert(beast::is_DynamicBuffer::value, + static_assert(beast::is_dynamic_buffer::value, "DynamicBuffer requirements not met"); - beast::async_completion< - ReadHandler, void(error_code)> completion{handler}; - read_frame_op{ - completion.handler, *this, fi, dynabuf}; - return completion.result.get(); + async_completion init{handler}; + read_frame_op>{ + init.completion_handler,*this, buffer}( + {}, 0, false); + return init.result.get(); } template template -void +bool stream:: -read_frame(frame_info& fi, DynamicBuffer& dynabuf) +read_frame(DynamicBuffer& buffer) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(beast::is_DynamicBuffer::value, + static_assert(beast::is_dynamic_buffer::value, "DynamicBuffer requirements not met"); error_code ec; - read_frame(fi, dynabuf, ec); + auto const fin = read_frame(buffer, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); + return fin; } template template -void +bool stream:: -read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec) +read_frame(DynamicBuffer& dynabuf, error_code& ec) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(beast::is_DynamicBuffer::value, + static_assert(beast::is_dynamic_buffer::value, "DynamicBuffer requirements not met"); using beast::detail::clamp; using boost::asio::buffer; using boost::asio::buffer_cast; using boost::asio::buffer_size; - close_code::value code{}; + close_code code{}; for(;;) { // Read frame header @@ -736,9 +753,9 @@ read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec) { fb.commit(boost::asio::read( stream_, fb.prepare(2), ec)); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) - return; + return false; { auto const n = read_fh1(fh, fb, code); if(code != close_code::none) @@ -747,16 +764,16 @@ read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec) { fb.commit(boost::asio::read( stream_, fb.prepare(n), ec)); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) - return; + return false; } } read_fh2(fh, fb, code); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) - return; + return false; if(code != close_code::none) goto do_close; } @@ -768,9 +785,9 @@ read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec) auto const mb = fb.prepare( static_cast(fh.len)); fb.commit(boost::asio::read(stream_, mb, ec)); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) - return; + return false; if(fh.mask) { detail::prepared_key key; @@ -780,52 +797,54 @@ read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec) fb.commit(static_cast(fh.len)); } // Process control frame - if(fh.op == opcode::ping) + if(fh.op == detail::opcode::ping) { ping_data payload; detail::read(payload, fb.data()); - fb.reset(); - if(ping_cb_) - ping_cb_(false, payload); - write_ping( - fb, opcode::pong, payload); + fb.consume(fb.size()); + if(ctrl_cb_) + ctrl_cb_(frame_type::ping, payload); + write_ping(fb, + detail::opcode::pong, payload); boost::asio::write(stream_, fb.data(), ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) - return; + return false; continue; } - else if(fh.op == opcode::pong) + else if(fh.op == detail::opcode::pong) { ping_data payload; detail::read(payload, fb.data()); - if(ping_cb_) - ping_cb_(true, payload); + if(ctrl_cb_) + ctrl_cb_(frame_type::pong, payload); continue; } - BOOST_ASSERT(fh.op == opcode::close); + BOOST_ASSERT(fh.op == detail::opcode::close); { detail::read(cr_, fb.data(), code); if(code != close_code::none) goto do_close; + if(ctrl_cb_) + ctrl_cb_(frame_type::close, cr_.reason); if(! wr_close_) { auto cr = cr_; if(cr.code == close_code::none) cr.code = close_code::normal; cr.reason = ""; - fb.reset(); + fb.consume(fb.size()); wr_close_ = true; - write_close(fb, cr); + write_close(fb, cr); boost::asio::write(stream_, fb.data(), ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) - return; + return false; } goto do_close; } } - if(fh.op != opcode::cont) + if(fh.op != detail::opcode::cont) rd_begin(); if(fh.len == 0 && ! fh.fin) { @@ -853,16 +872,16 @@ read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec) dynabuf.prepare(clamp(remain)); auto const bytes_transferred = stream_.read_some(b, ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) - return; + return false; BOOST_ASSERT(bytes_transferred > 0); remain -= bytes_transferred; - auto const pb = prepare_buffers( + auto const pb = buffer_prefix( bytes_transferred, b); if(fh.mask) detail::mask_inplace(pb, key); - if(rd_.op == opcode::text) + if(rd_.op == detail::opcode::text) { if(! rd_.utf8.write(pb) || (remain == 0 && fh.fin && @@ -885,9 +904,9 @@ read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec) auto const bytes_transferred = stream_.read_some(buffer(rd_.buf.get(), clamp(remain, rd_.buf_size)), ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) - return; + return false; remain -= bytes_transferred; auto const in = buffer( rd_.buf.get(), bytes_transferred); @@ -895,9 +914,9 @@ read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec) detail::mask_inplace(in, key); auto const prev = dynabuf.size(); detail::inflate(pmd_->zi, dynabuf, in, ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) - return; + return false; if(remain == 0 && fh.fin) { static std::uint8_t constexpr @@ -905,11 +924,11 @@ read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec) 0x00, 0x00, 0xff, 0xff }; detail::inflate(pmd_->zi, dynabuf, buffer(&empty_block[0], 4), ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) - return; + return false; } - if(rd_.op == opcode::text) + if(rd_.op == detail::opcode::text) { consuming_bufferszi.reset(); } - fi.op = rd_.op; - fi.fin = fh.fin; - return; + return fh.fin; } do_close: if(code != close_code::none) @@ -945,25 +962,41 @@ do_close: { wr_close_ = true; detail::frame_streambuf fb; - write_close(fb, code); + write_close(fb, code); boost::asio::write(stream_, fb.data(), ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) - return; + return false; } websocket_helpers::call_teardown(next_layer(), ec); - failed_ = ec != 0; + if(ec == boost::asio::error::eof) + { + // Rationale: + // http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error + ec.assign(0, ec.category()); + } + failed_ = !!ec; if(failed_) - return; + return false; ec = error::failed; failed_ = true; - return; + return false; } if(! ec) + { websocket_helpers::call_teardown(next_layer(), ec); + if(ec == boost::asio::error::eof) + { + // (See above) + ec.assign(0, ec.category()); + } + } if(! ec) ec = error::closed; - failed_ = ec != 0; + failed_ = !!ec; + if(failed_) + return false; + return true; } //------------------------------------------------------------------------------ @@ -974,73 +1007,58 @@ template template class stream::read_op { - struct data - { - bool cont; - stream& ws; - opcode& op; - DynamicBuffer& db; - frame_info fi; - int state = 0; - - data(Handler& handler, - stream& ws_, opcode& op_, - DynamicBuffer& sb_) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , ws(ws_) - , op(op_) - , db(sb_) - { - } - }; - - handler_ptr d_; + int state_ = 0; + stream& ws_; + DynamicBuffer& b_; + Handler h_; public: read_op(read_op&&) = default; read_op(read_op const&) = default; - template + template read_op(DeducedHandler&& h, - stream& ws, Args&&... args) - : d_(std::forward(h), - ws, std::forward(args)...) + stream& ws, DynamicBuffer& b) + : ws_(ws) + , b_(b) + , h_(std::forward(h)) { - (*this)(error_code{}, false); } - void operator()( - error_code const& ec, bool again = true); + void operator()(error_code const& ec, bool fin); friend void* asio_handler_allocate( std::size_t size, read_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->h_)); } friend void asio_handler_deallocate( void* p, std::size_t size, read_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->h_)); } friend bool asio_handler_is_continuation(read_op* op) { - return op->d_->cont; + using boost::asio::asio_handler_is_continuation; + return op->state_ >= 2 ? true: + asio_handler_is_continuation(std::addressof(op->h_)); } template friend void asio_handler_invoke(Function&& f, read_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke(f, std::addressof(op->h_)); } }; @@ -1048,88 +1066,83 @@ template template void stream::read_op:: -operator()(error_code const& ec, bool again) +operator()(error_code const& ec, bool fin) { - auto& d = *d_; - d.cont = d.cont || again; - while(! ec) + switch(state_) { - switch(d.state) - { - case 0: - // read payload - d.state = 1; - d.ws.async_read_frame( - d.fi, d.db, std::move(*this)); - return; + case 0: + state_ = 1; + goto do_read; - // got payload - case 1: - d.op = d.fi.op; - if(d.fi.fin) - goto upcall; - d.state = 0; - break; - } + case 1: + state_ = 2; + BEAST_FALLTHROUGH; + + case 2: + if(ec) + goto upcall; + if(fin) + goto upcall; + do_read: + return ws_.async_read_frame( + b_, std::move(*this)); } upcall: - d_.invoke(ec); + h_(ec); } template template -typename async_completion< - ReadHandler, void(error_code)>::result_type +async_return_type< + ReadHandler, void(error_code)> stream:: -async_read(opcode& op, - DynamicBuffer& dynabuf, ReadHandler&& handler) +async_read(DynamicBuffer& buffer, ReadHandler&& handler) { - static_assert(is_AsyncStream::value, + static_assert(is_async_stream::value, "AsyncStream requirements requirements not met"); - static_assert(beast::is_DynamicBuffer::value, + static_assert(beast::is_dynamic_buffer::value, "DynamicBuffer requirements not met"); - beast::async_completion< - ReadHandler, void(error_code) - > completion{handler}; - read_op{ - completion.handler, *this, op, dynabuf}; - return completion.result.get(); + async_completion init{handler}; + read_op>{ + init.completion_handler, *this, buffer}( + {}, false); + return init.result.get(); } template template void stream:: -read(opcode& op, DynamicBuffer& dynabuf) +read(DynamicBuffer& buffer) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(beast::is_DynamicBuffer::value, + static_assert(beast::is_dynamic_buffer::value, "DynamicBuffer requirements not met"); error_code ec; - read(op, dynabuf, ec); + read(buffer, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); } template template void stream:: -read(opcode& op, DynamicBuffer& dynabuf, error_code& ec) +read(DynamicBuffer& buffer, error_code& ec) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(beast::is_DynamicBuffer::value, + static_assert(beast::is_dynamic_buffer::value, "DynamicBuffer requirements not met"); - frame_info fi; for(;;) { - read_frame(fi, dynabuf, ec); + auto const fin = read_frame(buffer, ec); if(ec) break; - op = fi.op; - if(fi.fin) + if(fin) break; } } diff --git a/include/beast/websocket/impl/rfc6455.ipp b/include/beast/websocket/impl/rfc6455.ipp new file mode 100644 index 0000000000..e06b152c4b --- /dev/null +++ b/include/beast/websocket/impl/rfc6455.ipp @@ -0,0 +1,38 @@ +// +// 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_WEBSOCKET_IMPL_RFC6455_IPP +#define BEAST_WEBSOCKET_IMPL_RFC6455_IPP + +#include +#include + +namespace beast { +namespace websocket { + +template +bool +is_upgrade(http::header> const& req) +{ + if(req.version < 11) + return false; + if(req.method() != http::verb::get) + return false; + if(! http::token_list{req["Connection"]}.exists("upgrade")) + return false; + if(! http::token_list{req["Upgrade"]}.exists("websocket")) + return false; + if(! req.count(http::field::sec_websocket_version)) + return false; + return true; +} + +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/impl/ssl.ipp b/include/beast/websocket/impl/ssl.ipp index fc73530433..88e8a5e433 100644 --- a/include/beast/websocket/impl/ssl.ipp +++ b/include/beast/websocket/impl/ssl.ipp @@ -8,16 +8,11 @@ #ifndef BEAST_WEBSOCKET_IMPL_SSL_IPP_INCLUDED #define BEAST_WEBSOCKET_IMPL_SSL_IPP_INCLUDED -#include -#include -#include -#include +#include namespace beast { namespace websocket { -namespace detail { - /* See @@ -32,97 +27,6 @@ Behavior of ssl::stream regarding close_ to async_shutdown will complete with eof. */ -template -class teardown_ssl_op -{ - using stream_type = - boost::asio::ssl::stream; - - struct data - { - bool cont; - stream_type& stream; - int state = 0; - - data(Handler& handler, stream_type& stream_) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , stream(stream_) - { - } - }; - - handler_ptr d_; - -public: - template - explicit - teardown_ssl_op( - DeducedHandler&& h, stream_type& stream) - : d_(std::forward(h), stream) - { - (*this)(error_code{}, false); - } - - void - operator()(error_code ec, bool again = true); - - friend - void* asio_handler_allocate(std::size_t size, - teardown_ssl_op* op) - { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); - } - - friend - void asio_handler_deallocate(void* p, - std::size_t size, teardown_ssl_op* op) - { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); - } - - friend - bool asio_handler_is_continuation( - teardown_ssl_op* op) - { - return op->d_->cont; - } - - template - friend - void asio_handler_invoke(Function&& f, - teardown_ssl_op* op) - { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); - } -}; - -template -void -teardown_ssl_op:: -operator()(error_code ec, bool again) -{ - auto& d = *d_; - d.cont = d.cont || again; - while(!ec && d.state != 99) - { - switch(d.state) - { - case 0: - d.state = 99; - d.stream.async_shutdown(*this); - return; - } - } - d_.invoke(ec); -} - -} // detail - -//------------------------------------------------------------------------------ template void @@ -139,12 +43,7 @@ async_teardown(teardown_tag, boost::asio::ssl::stream& stream, TeardownHandler&& handler) { - static_assert(beast::is_CompletionHandler< - TeardownHandler, void(error_code)>::value, - "TeardownHandler requirements not met"); - detail::teardown_ssl_op::type>{std::forward( - handler), stream}; + stream.async_shutdown(std::forward(handler)); } } // websocket diff --git a/include/beast/websocket/impl/stream.ipp b/include/beast/websocket/impl/stream.ipp index fa932ffc7f..62bfe59522 100644 --- a/include/beast/websocket/impl/stream.ipp +++ b/include/beast/websocket/impl/stream.ipp @@ -8,27 +8,31 @@ #ifndef BEAST_WEBSOCKET_IMPL_STREAM_IPP #define BEAST_WEBSOCKET_IMPL_STREAM_IPP +#include #include #include #include +#include #include #include -#include #include #include -#include +#include #include -#include -#include -#include +#include +#include #include #include #include +#include +#include #include #include #include #include +#include + namespace beast { namespace websocket { @@ -47,20 +51,20 @@ set_option(permessage_deflate const& o) { if( o.server_max_window_bits > 15 || o.server_max_window_bits < 9) - throw std::invalid_argument{ - "invalid server_max_window_bits"}; + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid server_max_window_bits"}); if( o.client_max_window_bits > 15 || o.client_max_window_bits < 9) - throw std::invalid_argument{ - "invalid client_max_window_bits"}; + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid client_max_window_bits"}); if( o.compLevel < 0 || o.compLevel > 9) - throw std::invalid_argument{ - "invalid compLevel"}; + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid compLevel"}); if( o.memLevel < 1 || o.memLevel > 9) - throw std::invalid_argument{ - "invalid memLevel"}; + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid memLevel"}); pmd_opts_ = o; } @@ -83,20 +87,91 @@ reset() } template -http::request +template +void stream:: -build_request(boost::string_ref const& host, - boost::string_ref const& resource, std::string& key) +do_accept( + Decorator const& decorator, error_code& ec) { - http::request req; - req.url = { resource.data(), resource.size() }; + http::request_parser p; + http::read_header(next_layer(), + stream_.buffer(), p, ec); + if(ec) + return; + do_accept(p.get(), decorator, ec); +} + +template +template +void +stream:: +do_accept(http::header> const& req, + Decorator const& decorator, error_code& ec) +{ + auto const res = build_response(req, decorator); + http::write(stream_, res, ec); + if(ec) + return; + if(res.result() != http::status::switching_protocols) + { + ec = error::handshake_failed; + // VFALCO TODO Respect keep alive setting, perform + // teardown if Connection: close. + return; + } + pmd_read(pmd_config_, req); + open(role_type::server); +} + +template +template +void +stream:: +do_handshake(response_type* res_p, + string_view host, + string_view target, + RequestDecorator const& decorator, + error_code& ec) +{ + response_type res; + reset(); + detail::sec_ws_key_type key; + { + auto const req = build_request( + key, host, target, decorator); + pmd_read(pmd_config_, req); + http::write(stream_, req, ec); + } + if(ec) + return; + http::read(next_layer(), stream_.buffer(), res, ec); + if(ec) + return; + do_response(res, key, ec); + if(res_p) + *res_p = std::move(res); +} + +template +template +request_type +stream:: +build_request(detail::sec_ws_key_type& key, + string_view host, + string_view target, + Decorator const& decorator) +{ + request_type req; + req.target(target); req.version = 11; - req.method = "GET"; - req.fields.insert("Host", host); - req.fields.insert("Upgrade", "websocket"); - key = detail::make_sec_ws_key(maskgen_); - req.fields.insert("Sec-WebSocket-Key", key); - req.fields.insert("Sec-WebSocket-Version", "13"); + req.method(http::verb::get); + req.set(http::field::host, host); + req.set(http::field::upgrade, "websocket"); + req.set(http::field::connection, "upgrade"); + detail::make_sec_ws_key(key, maskgen_); + req.set(http::field::sec_websocket_key, key); + req.set(http::field::sec_websocket_version, "13"); if(pmd_opts_.client_enable) { detail::pmd_offer config; @@ -109,119 +184,502 @@ build_request(boost::string_ref const& host, pmd_opts_.server_no_context_takeover; config.client_no_context_takeover = pmd_opts_.client_no_context_takeover; - detail::pmd_write( - req.fields, config); + detail::pmd_write(req, config); } - d_(req); - http::prepare(req, http::connection::upgrade); + decorator(req); + if(! req.count(http::field::user_agent)) + req.set(http::field::user_agent, + BEAST_VERSION_STRING); return req; } template -template -http::response +template +response_type stream:: -build_response(http::request const& req) +build_response(http::header> const& req, + Decorator const& decorator) { + auto const decorate = + [&decorator](response_type& res) + { + decorator(res); + if(! res.count(http::field::server)) + { + BOOST_STATIC_ASSERT(sizeof(BEAST_VERSION_STRING) < 20); + static_string<20> s(BEAST_VERSION_STRING); + res.set(http::field::server, s); + } + }; auto err = [&](std::string const& text) { - http::response res; - res.status = 400; - res.reason = http::reason_string(res.status); + response_type res; res.version = req.version; + res.result(http::status::bad_request); res.body = text; - d_(res); - prepare(res, - (is_keep_alive(req) && keep_alive_) ? - http::connection::keep_alive : - http::connection::close); + res.prepare_payload(); + decorate(res); return res; }; if(req.version < 11) return err("HTTP version 1.1 required"); - if(req.method != "GET") + if(req.method() != http::verb::get) return err("Wrong method"); if(! is_upgrade(req)) return err("Expected Upgrade request"); - if(! req.fields.exists("Host")) + if(! req.count(http::field::host)) return err("Missing Host"); - if(! req.fields.exists("Sec-WebSocket-Key")) + if(! req.count(http::field::sec_websocket_key)) return err("Missing Sec-WebSocket-Key"); - if(! http::token_list{req.fields["Upgrade"]}.exists("websocket")) + if(! http::token_list{req[http::field::upgrade]}.exists("websocket")) return err("Missing websocket Upgrade token"); + auto const key = req[http::field::sec_websocket_key]; + if(key.size() > detail::sec_ws_key_type::max_size_n) + return err("Invalid Sec-WebSocket-Key"); { auto const version = - req.fields["Sec-WebSocket-Version"]; + req[http::field::sec_websocket_version]; if(version.empty()) return err("Missing Sec-WebSocket-Version"); if(version != "13") { - http::response res; - res.status = 426; - res.reason = http::reason_string(res.status); + response_type res; + res.result(http::status::upgrade_required); res.version = req.version; - res.fields.insert("Sec-WebSocket-Version", "13"); - d_(res); - prepare(res, - (is_keep_alive(req) && keep_alive_) ? - http::connection::keep_alive : - http::connection::close); + res.set(http::field::sec_websocket_version, "13"); + res.prepare_payload(); + decorate(res); return res; } } - http::response res; + + response_type res; { detail::pmd_offer offer; detail::pmd_offer unused; - pmd_read(offer, req.fields); - pmd_negotiate( - res.fields, unused, offer, pmd_opts_); + pmd_read(offer, req); + pmd_negotiate(res, unused, offer, pmd_opts_); } - res.status = 101; - res.reason = http::reason_string(res.status); + res.result(http::status::switching_protocols); res.version = req.version; - res.fields.insert("Upgrade", "websocket"); + res.set(http::field::upgrade, "websocket"); + res.set(http::field::connection, "upgrade"); { - auto const key = - req.fields["Sec-WebSocket-Key"]; - res.fields.insert("Sec-WebSocket-Accept", - detail::make_sec_ws_accept(key)); + detail::sec_ws_accept_type acc; + detail::make_sec_ws_accept(acc, key); + res.set(http::field::sec_websocket_accept, acc); } - res.fields.replace("Server", "Beast.WSProto"); - d_(res); - http::prepare(res, http::connection::upgrade); + decorate(res); return res; } template -template void stream:: -do_response(http::response const& res, - boost::string_ref const& key, error_code& ec) +do_response(http::header const& res, + detail::sec_ws_key_type const& key, error_code& ec) { - // VFALCO Review these error codes - auto fail = [&]{ ec = error::response_failed; }; - if(res.version < 11) - return fail(); - if(res.status != 101) - return fail(); - if(! is_upgrade(res)) - return fail(); - if(! http::token_list{res.fields["Upgrade"]}.exists("websocket")) - return fail(); - if(! res.fields.exists("Sec-WebSocket-Accept")) - return fail(); - if(res.fields["Sec-WebSocket-Accept"] != - detail::make_sec_ws_accept(key)) - return fail(); + bool const success = [&]() + { + if(res.version < 11) + return false; + if(res.result() != http::status::switching_protocols) + return false; + if(! http::token_list{res[http::field::connection]}.exists("upgrade")) + return false; + if(! http::token_list{res[http::field::upgrade]}.exists("websocket")) + return false; + if(res.count(http::field::sec_websocket_accept) != 1) + return false; + detail::sec_ws_accept_type acc; + detail::make_sec_ws_accept(acc, key); + if(acc.compare( + res[http::field::sec_websocket_accept]) != 0) + return false; + return true; + }(); + if(! success) + { + ec = error::handshake_failed; + return; + } + ec.assign(0, ec.category()); detail::pmd_offer offer; - pmd_read(offer, res.fields); + pmd_read(offer, res); // VFALCO see if offer satisfies pmd_config_, // return an error if not. pmd_config_ = offer; // overwrite for now - open(detail::role_type::client); + open(role_type::client); +} + +//------------------------------------------------------------------------------ + +template +void +stream:: +open(role_type role) +{ + // VFALCO TODO analyze and remove dupe code in reset() + role_ = role; + failed_ = false; + rd_.cont = false; + wr_close_ = false; + wr_block_ = nullptr; // should be nullptr on close anyway + ping_data_ = nullptr; // should be nullptr on close anyway + + wr_.cont = false; + wr_.buf_size = 0; + + if(((role_ == role_type::client && pmd_opts_.client_enable) || + (role_ == role_type::server && pmd_opts_.server_enable)) && + pmd_config_.accept) + { + pmd_normalize(pmd_config_); + pmd_.reset(new pmd_t); + if(role_ == role_type::client) + { + pmd_->zi.reset( + pmd_config_.server_max_window_bits); + pmd_->zo.reset( + pmd_opts_.compLevel, + pmd_config_.client_max_window_bits, + pmd_opts_.memLevel, + zlib::Strategy::normal); + } + else + { + pmd_->zi.reset( + pmd_config_.client_max_window_bits); + pmd_->zo.reset( + pmd_opts_.compLevel, + pmd_config_.server_max_window_bits, + pmd_opts_.memLevel, + zlib::Strategy::normal); + } + } +} + +template +void +stream:: +close() +{ + rd_.buf.reset(); + wr_.buf.reset(); + pmd_.reset(); +} + +// Read fixed frame header from buffer +// Requires at least 2 bytes +// +template +template +std::size_t +stream:: +read_fh1(detail::frame_header& fh, + DynamicBuffer& db, close_code& code) +{ + using boost::asio::buffer; + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + auto const err = + [&](close_code cv) + { + code = cv; + return 0; + }; + std::uint8_t b[2]; + BOOST_ASSERT(buffer_size(db.data()) >= sizeof(b)); + db.consume(buffer_copy(buffer(b), db.data())); + std::size_t need; + fh.len = b[1] & 0x7f; + switch(fh.len) + { + case 126: need = 2; break; + case 127: need = 8; break; + default: + need = 0; + } + fh.mask = (b[1] & 0x80) != 0; + if(fh.mask) + need += 4; + fh.op = static_cast< + detail::opcode>(b[0] & 0x0f); + fh.fin = (b[0] & 0x80) != 0; + fh.rsv1 = (b[0] & 0x40) != 0; + fh.rsv2 = (b[0] & 0x20) != 0; + fh.rsv3 = (b[0] & 0x10) != 0; + switch(fh.op) + { + case detail::opcode::binary: + case detail::opcode::text: + if(rd_.cont) + { + // new data frame when continuation expected + return err(close_code::protocol_error); + } + if((fh.rsv1 && ! pmd_) || + fh.rsv2 || fh.rsv3) + { + // reserved bits not cleared + return err(close_code::protocol_error); + } + if(pmd_) + pmd_->rd_set = fh.rsv1; + break; + + case detail::opcode::cont: + if(! rd_.cont) + { + // continuation without an active message + return err(close_code::protocol_error); + } + if(fh.rsv1 || fh.rsv2 || fh.rsv3) + { + // reserved bits not cleared + return err(close_code::protocol_error); + } + break; + + default: + if(is_reserved(fh.op)) + { + // reserved opcode + return err(close_code::protocol_error); + } + if(! fh.fin) + { + // fragmented control message + return err(close_code::protocol_error); + } + if(fh.len > 125) + { + // invalid length for control message + return err(close_code::protocol_error); + } + if(fh.rsv1 || fh.rsv2 || fh.rsv3) + { + // reserved bits not cleared + return err(close_code::protocol_error); + } + break; + } + // unmasked frame from client + if(role_ == role_type::server && ! fh.mask) + { + code = close_code::protocol_error; + return 0; + } + // masked frame from server + if(role_ == role_type::client && fh.mask) + { + code = close_code::protocol_error; + return 0; + } + code = close_code::none; + return need; +} + +// Decode variable frame header from buffer +// +template +template +void +stream:: +read_fh2(detail::frame_header& fh, + DynamicBuffer& db, close_code& code) +{ + using boost::asio::buffer; + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + using namespace boost::endian; + switch(fh.len) + { + case 126: + { + std::uint8_t b[2]; + BOOST_ASSERT(buffer_size(db.data()) >= sizeof(b)); + db.consume(buffer_copy(buffer(b), db.data())); + fh.len = detail::big_uint16_to_native(&b[0]); + // length not canonical + if(fh.len < 126) + { + code = close_code::protocol_error; + return; + } + break; + } + case 127: + { + std::uint8_t b[8]; + BOOST_ASSERT(buffer_size(db.data()) >= sizeof(b)); + db.consume(buffer_copy(buffer(b), db.data())); + fh.len = detail::big_uint64_to_native(&b[0]); + // length not canonical + if(fh.len < 65536) + { + code = close_code::protocol_error; + return; + } + break; + } + } + if(fh.mask) + { + std::uint8_t b[4]; + BOOST_ASSERT(buffer_size(db.data()) >= sizeof(b)); + db.consume(buffer_copy(buffer(b), db.data())); + fh.key = detail::little_uint32_to_native(&b[0]); + } + else + { + // initialize this otherwise operator== breaks + fh.key = 0; + } + if(! is_control(fh.op)) + { + if(fh.op != detail::opcode::cont) + { + rd_.size = 0; + rd_.op = fh.op; + } + else + { + if(rd_.size > (std::numeric_limits< + std::uint64_t>::max)() - fh.len) + { + code = close_code::too_big; + return; + } + } + rd_.cont = ! fh.fin; + } + code = close_code::none; +} + +template +void +stream:: +rd_begin() +{ + // Maintain the read buffer + if(pmd_) + { + if(! rd_.buf || rd_.buf_size != rd_buf_size_) + { + rd_.buf_size = rd_buf_size_; + rd_.buf = boost::make_unique_noinit< + std::uint8_t[]>(rd_.buf_size); + } + } +} + +template +void +stream:: +wr_begin() +{ + wr_.autofrag = wr_autofrag_; + wr_.compress = static_cast(pmd_); + + // Maintain the write buffer + if( wr_.compress || + role_ == role_type::client) + { + if(! wr_.buf || wr_.buf_size != wr_buf_size_) + { + wr_.buf_size = wr_buf_size_; + wr_.buf = boost::make_unique_noinit< + std::uint8_t[]>(wr_.buf_size); + } + } + else + { + wr_.buf_size = wr_buf_size_; + wr_.buf.reset(); + } +} + +template +template +void +stream:: +write_close(DynamicBuffer& db, close_reason const& cr) +{ + using namespace boost::endian; + detail::frame_header fh; + fh.op = detail::opcode::close; + fh.fin = true; + fh.rsv1 = false; + fh.rsv2 = false; + fh.rsv3 = false; + fh.len = cr.code == close_code::none ? + 0 : 2 + cr.reason.size(); + fh.mask = role_ == role_type::client; + if(fh.mask) + fh.key = maskgen_(); + detail::write(db, fh); + if(cr.code != close_code::none) + { + detail::prepared_key key; + if(fh.mask) + detail::prepare_key(key, fh.key); + { + std::uint8_t b[2]; + ::new(&b[0]) big_uint16_buf_t{ + (std::uint16_t)cr.code}; + auto d = db.prepare(2); + boost::asio::buffer_copy(d, + boost::asio::buffer(b)); + if(fh.mask) + detail::mask_inplace(d, key); + db.commit(2); + } + if(! cr.reason.empty()) + { + auto d = db.prepare(cr.reason.size()); + boost::asio::buffer_copy(d, + boost::asio::const_buffer( + cr.reason.data(), cr.reason.size())); + if(fh.mask) + detail::mask_inplace(d, key); + db.commit(cr.reason.size()); + } + } +} + +template +template +void +stream:: +write_ping(DynamicBuffer& db, + detail::opcode code, ping_data const& data) +{ + detail::frame_header fh; + fh.op = code; + fh.fin = true; + fh.rsv1 = false; + fh.rsv2 = false; + fh.rsv3 = false; + fh.len = data.size(); + fh.mask = role_ == role_type::client; + if(fh.mask) + fh.key = maskgen_(); + detail::write(db, fh); + if(data.empty()) + return; + detail::prepared_key key; + if(fh.mask) + detail::prepare_key(key, fh.key); + auto d = db.prepare(data.size()); + boost::asio::buffer_copy(d, + boost::asio::const_buffers_1( + data.data(), data.size())); + if(fh.mask) + detail::mask_inplace(d, key); + db.commit(data.size()); } } // websocket diff --git a/include/beast/websocket/impl/teardown.ipp b/include/beast/websocket/impl/teardown.ipp index cc8a46e368..f29a517b01 100644 --- a/include/beast/websocket/impl/teardown.ipp +++ b/include/beast/websocket/impl/teardown.ipp @@ -8,10 +8,12 @@ #ifndef BEAST_WEBSOCKET_IMPL_TEARDOWN_IPP #define BEAST_WEBSOCKET_IMPL_TEARDOWN_IPP -#include -#include -#include +#include #include +#include +#include +#include +#include #include namespace beast { @@ -33,10 +35,10 @@ class teardown_tcp_op int state = 0; data(Handler& handler, socket_type& socket_) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , socket(socket_) + : socket(socket_) { + using boost::asio::asio_handler_is_continuation; + cont = asio_handler_is_continuation(std::addressof(handler)); } }; @@ -59,16 +61,18 @@ public: void* asio_handler_allocate(std::size_t size, teardown_tcp_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + 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, teardown_tcp_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->d_.handler())); } friend @@ -82,8 +86,9 @@ public: void asio_handler_invoke(Function&& f, teardown_tcp_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->d_.handler())); } }; @@ -152,7 +157,7 @@ async_teardown(teardown_tag, boost::asio::ip::tcp::socket& socket, TeardownHandler&& handler) { - static_assert(beast::is_CompletionHandler< + static_assert(beast::is_completion_handler< TeardownHandler, void(error_code)>::value, "TeardownHandler requirements not met"); detail::teardown_tcp_op #include -#include +#include #include -#include #include -#include -#include -#include +#include +#include #include +#include #include +#include +#include +#include #include +#include +#include #include #include @@ -32,7 +36,6 @@ class stream::write_frame_op { struct data : op { - Handler& handler; bool cont; stream& ws; consuming_buffers cb; @@ -41,18 +44,17 @@ class stream::write_frame_op detail::fh_streambuf fh_buf; detail::prepared_key key; std::uint64_t remain; - int state = 0; + int step = 0; int entry_state; - data(Handler& handler_, stream& ws_, + data(Handler& handler, stream& ws_, bool fin_, Buffers const& bs) - : handler(handler_) - , cont(beast_asio_helpers:: - is_continuation(handler)) - , ws(ws_) + : ws(ws_) , cb(bs) , fin(fin_) { + using boost::asio::asio_handler_is_continuation; + cont = asio_handler_is_continuation(std::addressof(handler)); } }; @@ -68,39 +70,33 @@ public: : d_(std::forward(h), ws, std::forward(args)...) { - (*this)(error_code{}, 0, false); } void operator()() { - (*this)(error_code{}, 0, true); - } - - void operator()(error_code const& ec) - { - (*this)(ec, 0, true); + (*this)({}, 0, true); } void operator()(error_code ec, - std::size_t bytes_transferred); - - void operator()(error_code ec, - std::size_t bytes_transferred, bool again); + std::size_t bytes_transferred, + bool again = true); friend void* asio_handler_allocate( std::size_t size, write_frame_op* op) { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + 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_frame_op* op) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->d_.handler())); } friend @@ -113,24 +109,12 @@ public: friend void asio_handler_invoke(Function&& f, write_frame_op* op) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->d_.handler())); } }; -template -template -void -stream:: -write_frame_op:: -operator()(error_code ec, std::size_t bytes_transferred) -{ - auto& d = *d_; - if(ec) - d.ws.failed_ = true; - (*this)(ec, bytes_transferred, true); -} - template template void @@ -157,429 +141,496 @@ operator()(error_code ec, auto& d = *d_; d.cont = d.cont || again; if(ec) - goto upcall; - for(;;) { - switch(d.state) + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.ws.failed_ = true; + goto upcall; + } +loop: + switch(d.step) + { + case do_init: + if(! d.ws.wr_.cont) { - case do_init: - if(! d.ws.wr_.cont) - { - d.ws.wr_begin(); - d.fh.rsv1 = d.ws.wr_.compress; - } - else - { - d.fh.rsv1 = false; - } - d.fh.rsv2 = false; - d.fh.rsv3 = false; - d.fh.op = d.ws.wr_.cont ? - opcode::cont : d.ws.wr_opcode_; - d.fh.mask = - d.ws.role_ == detail::role_type::client; - - // entry_state determines which algorithm - // we will use to send. If we suspend, we - // will transition to entry_state + 1 on - // the resume. - if(d.ws.wr_.compress) - { - d.entry_state = do_deflate; - } - else if(! d.fh.mask) - { - if(! d.ws.wr_.autofrag) - { - d.entry_state = do_nomask_nofrag; - } - else - { - BOOST_ASSERT(d.ws.wr_.buf_size != 0); - d.remain = buffer_size(d.cb); - if(d.remain > d.ws.wr_.buf_size) - d.entry_state = do_nomask_frag; - else - d.entry_state = do_nomask_nofrag; - } - } - else - { - if(! d.ws.wr_.autofrag) - { - d.entry_state = do_mask_nofrag; - } - else - { - BOOST_ASSERT(d.ws.wr_.buf_size != 0); - d.remain = buffer_size(d.cb); - if(d.remain > d.ws.wr_.buf_size) - d.entry_state = do_mask_frag; - else - d.entry_state = do_mask_nofrag; - } - } - d.state = do_maybe_suspend; - break; - - //---------------------------------------------------------------------- - - case do_nomask_nofrag: - BOOST_ASSERT(! d.ws.wr_block_); - d.ws.wr_block_ = &d; - // [[fallthrough]] - - case do_nomask_nofrag + 1: - { - BOOST_ASSERT(d.ws.wr_block_ == &d); - d.fh.fin = d.fin; - d.fh.len = buffer_size(d.cb); - detail::write( - d.fh_buf, d.fh); - d.ws.wr_.cont = ! d.fin; - // Send frame - d.state = do_upcall; - boost::asio::async_write(d.ws.stream_, - buffer_cat(d.fh_buf.data(), d.cb), - std::move(*this)); - return; + d.ws.wr_begin(); + d.fh.rsv1 = d.ws.wr_.compress; } - - //---------------------------------------------------------------------- - - case do_nomask_frag: - BOOST_ASSERT(! d.ws.wr_block_); - d.ws.wr_block_ = &d; - // [[fallthrough]] - - case do_nomask_frag + 1: + else { - BOOST_ASSERT(d.ws.wr_block_ == &d); - auto const n = clamp( - d.remain, d.ws.wr_.buf_size); - d.remain -= n; - d.fh.len = n; - d.fh.fin = d.fin ? d.remain == 0 : false; - detail::write( - d.fh_buf, d.fh); - d.ws.wr_.cont = ! d.fin; - // Send frame - d.state = d.remain == 0 ? - do_upcall : do_nomask_frag + 2; - boost::asio::async_write(d.ws.stream_, - buffer_cat(d.fh_buf.data(), - prepare_buffers(n, d.cb)), - std::move(*this)); - return; - } - - case do_nomask_frag + 2: - d.cb.consume( - bytes_transferred - d.fh_buf.size()); - d.fh_buf.reset(); - d.fh.op = opcode::cont; - if(d.ws.wr_block_ == &d) - d.ws.wr_block_ = nullptr; - // Allow outgoing control frames to - // be sent in between message frames: - if(d.ws.rd_op_.maybe_invoke() || - d.ws.ping_op_.maybe_invoke()) - { - d.state = do_maybe_suspend; - d.ws.get_io_service().post( - std::move(*this)); - return; - } - d.state = d.entry_state; - break; - - //---------------------------------------------------------------------- - - case do_mask_nofrag: - BOOST_ASSERT(! d.ws.wr_block_); - d.ws.wr_block_ = &d; - // [[fallthrough]] - - case do_mask_nofrag + 1: - { - BOOST_ASSERT(d.ws.wr_block_ == &d); - d.remain = buffer_size(d.cb); - d.fh.fin = d.fin; - d.fh.len = d.remain; - d.fh.key = d.ws.maskgen_(); - detail::prepare_key(d.key, d.fh.key); - detail::write( - d.fh_buf, d.fh); - auto const n = - clamp(d.remain, d.ws.wr_.buf_size); - auto const b = - buffer(d.ws.wr_.buf.get(), n); - buffer_copy(b, d.cb); - detail::mask_inplace(b, d.key); - d.remain -= n; - d.ws.wr_.cont = ! d.fin; - // Send frame header and partial payload - d.state = d.remain == 0 ? - do_upcall : do_mask_nofrag + 2; - boost::asio::async_write(d.ws.stream_, - buffer_cat(d.fh_buf.data(), b), - std::move(*this)); - return; - } - - case do_mask_nofrag + 2: - { - d.cb.consume(d.ws.wr_.buf_size); - auto const n = - clamp(d.remain, d.ws.wr_.buf_size); - auto const b = - buffer(d.ws.wr_.buf.get(), n); - buffer_copy(b, d.cb); - detail::mask_inplace(b, d.key); - d.remain -= n; - // Send parial payload - if(d.remain == 0) - d.state = do_upcall; - boost::asio::async_write( - d.ws.stream_, b, std::move(*this)); - return; - } - - //---------------------------------------------------------------------- - - case do_mask_frag: - BOOST_ASSERT(! d.ws.wr_block_); - d.ws.wr_block_ = &d; - // [[fallthrough]] - - case do_mask_frag + 1: - { - BOOST_ASSERT(d.ws.wr_block_ == &d); - auto const n = clamp( - d.remain, d.ws.wr_.buf_size); - d.remain -= n; - d.fh.len = n; - d.fh.key = d.ws.maskgen_(); - d.fh.fin = d.fin ? d.remain == 0 : false; - detail::prepare_key(d.key, d.fh.key); - auto const b = buffer( - d.ws.wr_.buf.get(), n); - buffer_copy(b, d.cb); - detail::mask_inplace(b, d.key); - detail::write( - d.fh_buf, d.fh); - d.ws.wr_.cont = ! d.fin; - // Send frame - d.state = d.remain == 0 ? - do_upcall : do_mask_frag + 2; - boost::asio::async_write(d.ws.stream_, - buffer_cat(d.fh_buf.data(), b), - std::move(*this)); - return; - } - - case do_mask_frag + 2: - d.cb.consume( - bytes_transferred - d.fh_buf.size()); - d.fh_buf.reset(); - d.fh.op = opcode::cont; - BOOST_ASSERT(d.ws.wr_block_ == &d); - d.ws.wr_block_ = nullptr; - // Allow outgoing control frames to - // be sent in between message frames: - if(d.ws.rd_op_.maybe_invoke() || - d.ws.ping_op_.maybe_invoke()) - { - d.state = do_maybe_suspend; - d.ws.get_io_service().post( - std::move(*this)); - return; - } - d.state = d.entry_state; - break; - - //---------------------------------------------------------------------- - - case do_deflate: - BOOST_ASSERT(! d.ws.wr_block_); - d.ws.wr_block_ = &d; - // [[fallthrough]] - - case do_deflate + 1: - { - BOOST_ASSERT(d.ws.wr_block_ == &d); - auto b = buffer(d.ws.wr_.buf.get(), - d.ws.wr_.buf_size); - auto const more = detail::deflate( - d.ws.pmd_->zo, b, d.cb, d.fin, ec); - d.ws.failed_ = ec != 0; - if(d.ws.failed_) - goto upcall; - auto const n = buffer_size(b); - if(n == 0) - { - // The input was consumed, but there - // is no output due to compression - // latency. - BOOST_ASSERT(! d.fin); - BOOST_ASSERT(buffer_size(d.cb) == 0); - - // We can skip the dispatch if the - // asynchronous initiation function is - // not on call stack but its hard to - // figure out so be safe and dispatch. - d.state = do_upcall; - d.ws.get_io_service().post(std::move(*this)); - return; - } - if(d.fh.mask) - { - d.fh.key = d.ws.maskgen_(); - detail::prepared_key key; - detail::prepare_key(key, d.fh.key); - detail::mask_inplace(b, key); - } - d.fh.fin = ! more; - d.fh.len = n; - detail::fh_streambuf fh_buf; - detail::write(fh_buf, d.fh); - d.ws.wr_.cont = ! d.fin; - // Send frame - d.state = more ? - do_deflate + 2 : do_deflate + 3; - boost::asio::async_write(d.ws.stream_, - buffer_cat(fh_buf.data(), b), - std::move(*this)); - return; - } - - case do_deflate + 2: - d.fh.op = opcode::cont; d.fh.rsv1 = false; - BOOST_ASSERT(d.ws.wr_block_ == &d); - d.ws.wr_block_ = nullptr; - // Allow outgoing control frames to - // be sent in between message frames: - if(d.ws.rd_op_.maybe_invoke() || - d.ws.ping_op_.maybe_invoke()) - { - d.state = do_maybe_suspend; - d.ws.get_io_service().post( - std::move(*this)); - return; - } - d.state = d.entry_state; - break; + } + d.fh.rsv2 = false; + d.fh.rsv3 = false; + d.fh.op = d.ws.wr_.cont ? + detail::opcode::cont : d.ws.wr_opcode_; + d.fh.mask = + d.ws.role_ == role_type::client; - case do_deflate + 3: - if(d.fh.fin && ( - (d.ws.role_ == detail::role_type::client && - d.ws.pmd_config_.client_no_context_takeover) || - (d.ws.role_ == detail::role_type::server && - d.ws.pmd_config_.server_no_context_takeover))) - d.ws.pmd_->zo.reset(); - goto upcall; - - //---------------------------------------------------------------------- - - case do_maybe_suspend: + // entry_state determines which algorithm + // we will use to send. If we suspend, we + // will transition to entry_state + 1 on + // the resume. + if(d.ws.wr_.compress) { - if(d.ws.wr_block_) - { - // suspend - d.state = do_maybe_suspend + 1; - d.ws.wr_op_.template emplace< - write_frame_op>(std::move(*this)); - return; - } - if(d.ws.failed_ || d.ws.wr_close_) - { - // call handler - d.state = do_upcall; - d.ws.get_io_service().post( - bind_handler(std::move(*this), - boost::asio::error::operation_aborted)); - return; - } - d.state = d.entry_state; - break; + d.entry_state = do_deflate; } - - case do_maybe_suspend + 1: - BOOST_ASSERT(! d.ws.wr_block_); - d.ws.wr_block_ = &d; - d.state = do_maybe_suspend + 2; - // The current context is safe but might not be - // the same as the one for this operation (since - // we are being called from a write operation). - // Call post to make sure we are invoked the same - // way as the final handler for this operation. - d.ws.get_io_service().post(bind_handler( - std::move(*this), ec)); - return; - - case do_maybe_suspend + 2: - BOOST_ASSERT(d.ws.wr_block_ == &d); - if(d.ws.failed_ || d.ws.wr_close_) + else if(! d.fh.mask) + { + if(! d.ws.wr_.autofrag) { - // call handler - ec = boost::asio::error::operation_aborted; - goto upcall; + d.entry_state = do_nomask_nofrag; } - d.state = d.entry_state + 1; - break; + else + { + BOOST_ASSERT(d.ws.wr_.buf_size != 0); + d.remain = buffer_size(d.cb); + if(d.remain > d.ws.wr_.buf_size) + d.entry_state = do_nomask_frag; + else + d.entry_state = do_nomask_nofrag; + } + } + else + { + if(! d.ws.wr_.autofrag) + { + d.entry_state = do_mask_nofrag; + } + else + { + BOOST_ASSERT(d.ws.wr_.buf_size != 0); + d.remain = buffer_size(d.cb); + if(d.remain > d.ws.wr_.buf_size) + d.entry_state = do_mask_frag; + else + d.entry_state = do_mask_nofrag; + } + } + d.step = do_maybe_suspend; + goto loop; - //---------------------------------------------------------------------- + //---------------------------------------------------------------------- - case do_upcall: + case do_nomask_nofrag: + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.fh.fin = d.fin; + d.fh.len = buffer_size(d.cb); + detail::write( + d.fh_buf, d.fh); + d.ws.wr_.cont = ! d.fin; + // Send frame + d.step = do_upcall; + return boost::asio::async_write(d.ws.stream_, + buffer_cat(d.fh_buf.data(), d.cb), + std::move(*this)); + + //---------------------------------------------------------------------- + + go_nomask_frag: + case do_nomask_frag: + { + BOOST_ASSERT(d.ws.wr_block_ == &d); + auto const n = clamp( + d.remain, d.ws.wr_.buf_size); + d.remain -= n; + d.fh.len = n; + d.fh.fin = d.fin ? d.remain == 0 : false; + detail::write( + d.fh_buf, d.fh); + d.ws.wr_.cont = ! d.fin; + // Send frame + d.step = d.remain == 0 ? + do_upcall : do_nomask_frag + 1; + return boost::asio::async_write( + d.ws.stream_, buffer_cat( + d.fh_buf.data(), buffer_prefix( + n, d.cb)), std::move(*this)); + } + + case do_nomask_frag + 1: + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.ws.wr_block_ = nullptr; + d.cb.consume( + bytes_transferred - d.fh_buf.size()); + d.fh_buf.consume(d.fh_buf.size()); + d.fh.op = detail::opcode::cont; + // Allow outgoing control frames to + // be sent in between message frames + if( d.ws.close_op_.maybe_invoke() || + d.ws.rd_op_.maybe_invoke() || + d.ws.ping_op_.maybe_invoke()) + { + d.step = do_maybe_suspend; + return d.ws.get_io_service().post( + std::move(*this)); + } + d.ws.wr_block_ = &d; + goto go_nomask_frag; + + //---------------------------------------------------------------------- + + case do_mask_nofrag: + { + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.remain = buffer_size(d.cb); + d.fh.fin = d.fin; + d.fh.len = d.remain; + d.fh.key = d.ws.maskgen_(); + detail::prepare_key(d.key, d.fh.key); + detail::write( + d.fh_buf, d.fh); + auto const n = + clamp(d.remain, d.ws.wr_.buf_size); + auto const b = + buffer(d.ws.wr_.buf.get(), n); + buffer_copy(b, d.cb); + detail::mask_inplace(b, d.key); + d.remain -= n; + d.ws.wr_.cont = ! d.fin; + // Send frame header and partial payload + d.step = d.remain == 0 ? + do_upcall : do_mask_nofrag + 1; + return boost::asio::async_write( + d.ws.stream_, buffer_cat(d.fh_buf.data(), + b), std::move(*this)); + } + + case do_mask_nofrag + 1: + { + d.cb.consume(d.ws.wr_.buf_size); + auto const n = + clamp(d.remain, d.ws.wr_.buf_size); + auto const b = + buffer(d.ws.wr_.buf.get(), n); + buffer_copy(b, d.cb); + detail::mask_inplace(b, d.key); + d.remain -= n; + // Send partial payload + if(d.remain == 0) + d.step = do_upcall; + return boost::asio::async_write( + d.ws.stream_, b, std::move(*this)); + } + + //---------------------------------------------------------------------- + + go_mask_frag: + case do_mask_frag: + { + BOOST_ASSERT(d.ws.wr_block_ == &d); + auto const n = clamp( + d.remain, d.ws.wr_.buf_size); + d.remain -= n; + d.fh.len = n; + d.fh.key = d.ws.maskgen_(); + d.fh.fin = d.fin ? d.remain == 0 : false; + detail::prepare_key(d.key, d.fh.key); + auto const b = buffer( + d.ws.wr_.buf.get(), n); + buffer_copy(b, d.cb); + detail::mask_inplace(b, d.key); + detail::write( + d.fh_buf, d.fh); + d.ws.wr_.cont = ! d.fin; + // Send frame + d.step = d.remain == 0 ? + do_upcall : do_mask_frag + 1; + return boost::asio::async_write( + d.ws.stream_, buffer_cat( + d.fh_buf.data(), b), + std::move(*this)); + } + + case do_mask_frag + 1: + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.ws.wr_block_ = nullptr; + d.cb.consume( + bytes_transferred - d.fh_buf.size()); + d.fh_buf.consume(d.fh_buf.size()); + d.fh.op = detail::opcode::cont; + // Allow outgoing control frames to + // be sent in between message frames: + if( d.ws.close_op_.maybe_invoke() || + d.ws.rd_op_.maybe_invoke() || + d.ws.ping_op_.maybe_invoke()) + { + d.step = do_maybe_suspend; + d.ws.get_io_service().post( + std::move(*this)); + return; + } + d.ws.wr_block_ = &d; + goto go_mask_frag; + + //---------------------------------------------------------------------- + + go_deflate: + case do_deflate: + { + BOOST_ASSERT(d.ws.wr_block_ == &d); + auto b = buffer(d.ws.wr_.buf.get(), + d.ws.wr_.buf_size); + auto const more = detail::deflate( + d.ws.pmd_->zo, b, d.cb, d.fin, ec); + d.ws.failed_ = !!ec; + if(d.ws.failed_) + goto upcall; + auto const n = buffer_size(b); + if(n == 0) + { + // The input was consumed, but there + // is no output due to compression + // latency. + BOOST_ASSERT(! d.fin); + BOOST_ASSERT(buffer_size(d.cb) == 0); + + // We can skip the dispatch if the + // asynchronous initiation function is + // not on call stack but its hard to + // figure out so be safe and dispatch. + d.step = do_upcall; + d.ws.get_io_service().post(std::move(*this)); + return; + } + if(d.fh.mask) + { + d.fh.key = d.ws.maskgen_(); + detail::prepared_key key; + detail::prepare_key(key, d.fh.key); + detail::mask_inplace(b, key); + } + d.fh.fin = ! more; + d.fh.len = n; + detail::fh_streambuf fh_buf; + detail::write(fh_buf, d.fh); + d.ws.wr_.cont = ! d.fin; + // Send frame + d.step = more ? + do_deflate + 1 : do_deflate + 2; + boost::asio::async_write(d.ws.stream_, + buffer_cat(fh_buf.data(), b), + std::move(*this)); + return; + } + + case do_deflate + 1: + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.ws.wr_block_ = nullptr; + d.fh.op = detail::opcode::cont; + d.fh.rsv1 = false; + // Allow outgoing control frames to + // be sent in between message frames: + if( d.ws.close_op_.maybe_invoke() || + d.ws.rd_op_.maybe_invoke() || + d.ws.ping_op_.maybe_invoke()) + { + d.step = do_maybe_suspend; + d.ws.get_io_service().post( + std::move(*this)); + return; + } + d.ws.wr_block_ = &d; + goto go_deflate; + + case do_deflate + 2: + BOOST_ASSERT(d.ws.wr_block_ == &d); + if(d.fh.fin && ( + (d.ws.role_ == role_type::client && + d.ws.pmd_config_.client_no_context_takeover) || + (d.ws.role_ == role_type::server && + d.ws.pmd_config_.server_no_context_takeover))) + d.ws.pmd_->zo.reset(); + goto upcall; + + //---------------------------------------------------------------------- + + case do_maybe_suspend: + if(d.ws.wr_block_) + { + // suspend + BOOST_ASSERT(d.ws.wr_block_ != &d); + d.step = do_maybe_suspend + 1; + d.ws.wr_op_.emplace(std::move(*this)); + return; + } + d.ws.wr_block_ = &d; + if(d.ws.failed_ || d.ws.wr_close_) + { + // call handler + return d.ws.get_io_service().post( + bind_handler(std::move(*this), + boost::asio::error::operation_aborted, 0)); + } + d.step = d.entry_state; + goto loop; + + case do_maybe_suspend + 1: + BOOST_ASSERT(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + d.step = do_maybe_suspend + 2; + // The current context is safe but might not be + // the same as the one for this operation (since + // we are being called from a write operation). + // Call post to make sure we are invoked the same + // way as the final handler for this operation. + d.ws.get_io_service().post(bind_handler( + std::move(*this), ec, 0)); + return; + + case do_maybe_suspend + 2: + BOOST_ASSERT(d.ws.wr_block_ == &d); + if(d.ws.failed_ || d.ws.wr_close_) + { + // call handler + ec = boost::asio::error::operation_aborted; goto upcall; } + d.step = d.entry_state; + goto loop; + + //---------------------------------------------------------------------- + + case do_upcall: + goto upcall; } upcall: if(d.ws.wr_block_ == &d) d.ws.wr_block_ = nullptr; - d.ws.rd_op_.maybe_invoke() || + d.ws.close_op_.maybe_invoke() || + d.ws.rd_op_.maybe_invoke() || d.ws.ping_op_.maybe_invoke(); d_.invoke(ec); } +//------------------------------------------------------------------------------ + template -template -typename async_completion< - WriteHandler, void(error_code)>::result_type -stream:: -async_write_frame(bool fin, - ConstBufferSequence const& bs, WriteHandler&& handler) +template +class stream::write_op { - static_assert(is_AsyncStream::value, - "AsyncStream requirements not met"); - static_assert(beast::is_ConstBufferSequence< - ConstBufferSequence>::value, - "ConstBufferSequence requirements not met"); - beast::async_completion< - WriteHandler, void(error_code) - > completion{handler}; - write_frame_op{completion.handler, - *this, fin, bs}; - return completion.result.get(); + struct data : op + { + int step = 0; + stream& ws; + consuming_buffers cb; + std::size_t remain; + + data(Handler&, stream& ws_, + Buffers const& bs) + : ws(ws_) + , cb(bs) + , remain(boost::asio::buffer_size(cb)) + { + } + }; + + handler_ptr d_; + +public: + write_op(write_op&&) = default; + write_op(write_op const&) = default; + + template + explicit + write_op(DeducedHandler&& h, + stream& ws, Args&&... args) + : d_(std::forward(h), + ws, std::forward(args)...) + { + } + + void operator()(error_code ec); + + friend + void* asio_handler_allocate( + std::size_t size, write_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_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_op* op) + { + using boost::asio::asio_handler_is_continuation; + return op->d_->step > 2 || + asio_handler_is_continuation( + std::addressof(op->d_.handler())); + } + + template + friend + void asio_handler_invoke(Function&& f, write_op* op) + { + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->d_.handler())); + } +}; + +template +template +void +stream:: +write_op:: +operator()(error_code ec) +{ + auto& d = *d_; + switch(d.step) + { + case 2: + d.step = 3; + BEAST_FALLTHROUGH; + case 3: + case 0: + { + auto const n = d.remain; + d.remain -= n; + auto const fin = d.remain <= 0; + if(fin) + d.step = d.step ? 4 : 1; + else + d.step = d.step ? 3 : 2; + auto const pb = buffer_prefix(n, d.cb); + d.cb.consume(n); + return d.ws.async_write_frame( + fin, pb, std::move(*this)); + } + + case 1: + case 4: + break; + } + d_.invoke(ec); } +//------------------------------------------------------------------------------ + template template void stream:: write_frame(bool fin, ConstBufferSequence const& buffers) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(beast::is_ConstBufferSequence< + static_assert(beast::is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); error_code ec; write_frame(fin, buffers, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); } template @@ -589,9 +640,9 @@ stream:: write_frame(bool fin, ConstBufferSequence const& buffers, error_code& ec) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(beast::is_ConstBufferSequence< + static_assert(beast::is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); using beast::detail::clamp; @@ -610,8 +661,9 @@ write_frame(bool fin, } fh.rsv2 = false; fh.rsv3 = false; - fh.op = wr_.cont ? opcode::cont : wr_opcode_; - fh.mask = role_ == detail::role_type::client; + fh.op = wr_.cont ? + detail::opcode::cont : wr_opcode_; + fh.mask = role_ == role_type::client; auto remain = buffer_size(buffers); if(wr_.compress) { @@ -623,7 +675,7 @@ write_frame(bool fin, wr_.buf.get(), wr_.buf_size); auto const more = detail::deflate( pmd_->zo, b, cb, fin, ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) return; auto const n = buffer_size(b); @@ -647,22 +699,22 @@ write_frame(bool fin, fh.fin = ! more; fh.len = n; detail::fh_streambuf fh_buf; - detail::write(fh_buf, fh); + detail::write(fh_buf, fh); wr_.cont = ! fin; boost::asio::write(stream_, buffer_cat(fh_buf.data(), b), ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) return; if(! more) break; - fh.op = opcode::cont; + fh.op = detail::opcode::cont; fh.rsv1 = false; } if(fh.fin && ( - (role_ == detail::role_type::client && + (role_ == role_type::client && pmd_config_.client_no_context_takeover) || - (role_ == detail::role_type::server && + (role_ == role_type::server && pmd_config_.server_no_context_takeover))) pmd_->zo.reset(); return; @@ -675,11 +727,11 @@ write_frame(bool fin, fh.fin = fin; fh.len = remain; detail::fh_streambuf fh_buf; - detail::write(fh_buf, fh); + detail::write(fh_buf, fh); wr_.cont = ! fin; boost::asio::write(stream_, buffer_cat(fh_buf.data(), buffers), ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) return; } @@ -696,17 +748,17 @@ write_frame(bool fin, fh.len = n; fh.fin = fin ? remain == 0 : false; detail::fh_streambuf fh_buf; - detail::write(fh_buf, fh); + detail::write(fh_buf, fh); wr_.cont = ! fin; boost::asio::write(stream_, buffer_cat(fh_buf.data(), - prepare_buffers(n, cb)), ec); - failed_ = ec != 0; + buffer_prefix(n, cb)), ec); + failed_ = !!ec; if(failed_) return; if(remain == 0) break; - fh.op = opcode::cont; + fh.op = detail::opcode::cont; cb.consume(n); } } @@ -721,7 +773,7 @@ write_frame(bool fin, detail::prepared_key key; detail::prepare_key(key, fh.key); detail::fh_streambuf fh_buf; - detail::write(fh_buf, fh); + detail::write(fh_buf, fh); consuming_buffers< ConstBufferSequence> cb{buffers}; { @@ -734,7 +786,7 @@ write_frame(bool fin, wr_.cont = ! fin; boost::asio::write(stream_, buffer_cat(fh_buf.data(), b), ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) return; } @@ -747,7 +799,7 @@ write_frame(bool fin, remain -= n; detail::mask_inplace(b, key); boost::asio::write(stream_, b, ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) return; } @@ -772,162 +824,59 @@ write_frame(bool fin, fh.fin = fin ? remain == 0 : false; wr_.cont = ! fh.fin; detail::fh_streambuf fh_buf; - detail::write(fh_buf, fh); + detail::write(fh_buf, fh); boost::asio::write(stream_, buffer_cat(fh_buf.data(), b), ec); - failed_ = ec != 0; + failed_ = !!ec; if(failed_) return; if(remain == 0) break; - fh.op = opcode::cont; + fh.op = detail::opcode::cont; cb.consume(n); } return; } } -//------------------------------------------------------------------------------ - -template -template -class stream::write_op -{ - struct data : op - { - bool cont; - stream& ws; - consuming_buffers cb; - std::size_t remain; - int state = 0; - - data(Handler& handler, stream& ws_, - Buffers const& bs) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , ws(ws_) - , cb(bs) - , remain(boost::asio::buffer_size(cb)) - { - } - }; - - handler_ptr d_; - -public: - write_op(write_op&&) = default; - write_op(write_op const&) = default; - - template - explicit - write_op(DeducedHandler&& h, - stream& ws, Args&&... args) - : d_(std::forward(h), - ws, std::forward(args)...) - { - (*this)(error_code{}, false); - } - - void operator()(error_code ec, bool again = true); - - friend - void* asio_handler_allocate( - std::size_t size, write_op* op) - { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); - } - - friend - void asio_handler_deallocate( - void* p, std::size_t size, write_op* op) - { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); - } - - friend - bool asio_handler_is_continuation(write_op* op) - { - return op->d_->cont; - } - - template - friend - void asio_handler_invoke(Function&& f, write_op* op) - { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); - } -}; - -template -template -void -stream:: -write_op:: -operator()(error_code ec, bool again) -{ - auto& d = *d_; - d.cont = d.cont || again; - if(! ec) - { - switch(d.state) - { - case 0: - { - auto const n = d.remain; - d.remain -= n; - auto const fin = d.remain <= 0; - if(fin) - d.state = 99; - auto const pb = prepare_buffers(n, d.cb); - d.cb.consume(n); - d.ws.async_write_frame(fin, pb, std::move(*this)); - return; - } - - case 99: - break; - } - } - d_.invoke(ec); -} - template template -typename async_completion< - WriteHandler, void(error_code)>::result_type +async_return_type< + WriteHandler, void(error_code)> stream:: -async_write(ConstBufferSequence const& bs, WriteHandler&& handler) +async_write_frame(bool fin, + ConstBufferSequence const& bs, WriteHandler&& handler) { - static_assert(is_AsyncStream::value, + static_assert(is_async_stream::value, "AsyncStream requirements not met"); - static_assert(beast::is_ConstBufferSequence< + static_assert(beast::is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); - beast::async_completion< - WriteHandler, void(error_code)> completion{handler}; - write_op{ - completion.handler, *this, bs}; - return completion.result.get(); + async_completion init{handler}; + write_frame_op>{init.completion_handler, + *this, fin, bs}({}, 0, false); + return init.result.get(); } +//------------------------------------------------------------------------------ + template template void stream:: write(ConstBufferSequence const& buffers) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(beast::is_ConstBufferSequence< + static_assert(beast::is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); error_code ec; write(buffers, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); } template @@ -936,14 +885,36 @@ void stream:: write(ConstBufferSequence const& buffers, error_code& ec) { - static_assert(is_SyncStream::value, + static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(beast::is_ConstBufferSequence< + static_assert(beast::is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); write_frame(true, buffers, ec); } +template +template +async_return_type< + WriteHandler, void(error_code)> +stream:: +async_write( + ConstBufferSequence const& bs, WriteHandler&& handler) +{ + static_assert(is_async_stream::value, + "AsyncStream requirements not met"); + static_assert(beast::is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + async_completion init{handler}; + write_op>{ + init.completion_handler, *this, bs}( + error_code{}); + return init.result.get(); +} + } // websocket } // beast diff --git a/include/beast/websocket/option.hpp b/include/beast/websocket/option.hpp index 005ae7b238..258f5565bc 100644 --- a/include/beast/websocket/option.hpp +++ b/include/beast/websocket/option.hpp @@ -10,8 +10,8 @@ #include #include -#include #include +#include #include #include #include @@ -22,177 +22,6 @@ namespace beast { namespace websocket { -/** Automatic fragmentation option. - - Determines if outgoing message payloads are broken up into - multiple pieces. - - When the automatic fragmentation size is turned on, outgoing - message payloads are broken up into multiple frames no larger - than the write buffer size. - - The default setting is to fragment messages. - - @note Objects of this type are used with - @ref beast::websocket::stream::set_option. - - @par Example - Setting the automatic fragmentation option: - @code - ... - websocket::stream stream(ios); - stream.set_option(auto_fragment{true}); - @endcode -*/ -#if GENERATING_DOCS -using auto_fragment = implementation_defined; -#else -struct auto_fragment -{ - bool value; - - explicit - auto_fragment(bool v) - : value(v) - { - } -}; -#endif - -/** HTTP decorator option. - - The decorator transforms the HTTP requests and responses used - when requesting or responding to the WebSocket Upgrade. This may - be used to set or change header fields. For example to set the - Server or User-Agent fields. The default setting applies no - transformation to the HTTP message. - - The context in which the decorator is called depends on the - type of operation performed: - - @li For synchronous operations, the implementation will call the - decorator before the operation unblocks. - - @li For asynchronous operations, the implementation guarantees - that calls to the decorator will be made from the same implicit - or explicit strand used to call the asynchronous initiation - function. - - The default setting is no decorator. - - @note Objects of this type are used with - @ref beast::websocket::stream::set_option. - - @par Example - Setting the decorator. - @code - struct identity - { - template - void - operator()(http::message& m) - { - if(isRequest) - m.fields.replace("User-Agent", "MyClient"); - else - m.fields.replace("Server", "MyServer"); - } - }; - ... - websocket::stream ws(ios); - ws.set_option(decorate(identity{})); - @endcode -*/ -#if GENERATING_DOCS -using decorate = implementation_defined; -#else -using decorate = detail::decorator_type; -#endif - -/** Keep-alive option. - - Determines if the connection is closed after a failed upgrade - request. - - This setting only affects the behavior of HTTP requests that - implicitly or explicitly ask for a keepalive. For HTTP requests - that indicate the connection should be closed, the connection is - closed as per rfc7230. - - The default setting is to close connections after a failed - upgrade request. - - @note Objects of this type are used with - @ref beast::websocket::stream::set_option. - - @par Example - Setting the keep alive option. - @code - ... - websocket::stream ws(ios); - ws.set_option(keep_alive{8192}); - @endcode -*/ -#if GENERATING_DOCS -using keep_alive = implementation_defined; -#else -struct keep_alive -{ - bool value; - - explicit - keep_alive(bool v) - : value(v) - { - } -}; -#endif - -/** Message type option. - - This controls the opcode set for outgoing messages. Valid - choices are opcode::binary or opcode::text. The setting is - only applied at the start when a caller begins a new message. - Changing the opcode after a message is started will only - take effect after the current message being sent is complete. - - The default setting is opcode::text. - - @note Objects of this type are used with - @ref beast::websocket::stream::set_option. - - @par Example - Setting the message type to binary. - @code - ... - websocket::stream ws(ios); - ws.set_option(message_type{opcode::binary}); - @endcode -*/ -#if GENERATING_DOCS -using message_type = implementation_defined; -#else -struct message_type -{ - opcode value; - - explicit - message_type(opcode op) - { - if(op != opcode::binary && op != opcode::text) - throw beast::detail::make_exception( - "bad opcode", __FILE__, __LINE__); - value = op; - } -}; -#endif - -namespace detail { - -using ping_cb = std::function; - -} // detail - /** permessage-deflate extension options. These settings control the permessage-deflate extension, @@ -234,183 +63,6 @@ struct permessage_deflate int memLevel = 4; }; -/** Ping callback option. - - Sets the callback to be invoked whenever a ping or pong is - received during a call to one of the following functions: - - @li @ref beast::websocket::stream::read - @li @ref beast::websocket::stream::read_frame - @li @ref beast::websocket::stream::async_read - @li @ref beast::websocket::stream::async_read_frame - - Unlike completion handlers, the callback will be invoked - for each received ping and pong during a call to any - synchronous or asynchronous read function. The operation is - passive, with no associated error code, and triggered by reads. - - The signature of the callback must be: - @code - void - callback( - bool is_pong, // `true` if this is a pong - ping_data const& payload // Payload of the pong frame - ); - @endcode - - The value of `is_pong` will be `true` if a pong control frame - is received, and `false` if a ping control frame is received. - - If the read operation receiving a ping or pong frame is an - asynchronous operation, the callback will be invoked using - the same method as that used to invoke the final handler. - - @note Objects of this type are used with - @ref beast::websocket::stream::set_option. - To remove the ping callback, construct the option with - no parameters: `set_option(ping_callback{})` -*/ -#if GENERATING_DOCS -using ping_callback = implementation_defined; -#else -struct ping_callback -{ - detail::ping_cb value; - - ping_callback() = default; - ping_callback(ping_callback&&) = default; - ping_callback(ping_callback const&) = default; - - explicit - ping_callback(detail::ping_cb f) - : value(std::move(f)) - { - } -}; -#endif - -/** Read buffer size option. - - Sets the size of the read buffer used by the implementation to - receive frames. The read buffer is needed when permessage-deflate - is used. - - Lowering the size of the buffer can decrease the memory requirements - for each connection, while increasing the size of the buffer can reduce - the number of calls made to the next layer to read data. - - The default setting is 4096. The minimum value is 8. - - @note Objects of this type are used with - @ref beast::websocket::stream::set_option. - - @par Example - Setting the read buffer size. - @code - ... - websocket::stream ws(ios); - ws.set_option(read_buffer_size{16 * 1024}); - @endcode -*/ -#if GENERATING_DOCS -using read_buffer_size = implementation_defined; -#else -struct read_buffer_size -{ - std::size_t value; - - explicit - read_buffer_size(std::size_t n) - : value(n) - { - if(n < 8) - throw beast::detail::make_exception( - "read buffer size is too small", __FILE__, __LINE__); - } -}; -#endif - -/** Maximum incoming message size option. - - Sets the largest permissible incoming message size. Message - frame fields indicating a size that would bring the total - message size over this limit will cause a protocol failure. - - The default setting is 16 megabytes. A value of zero indicates - a limit of the maximum value of a `std::uint64_t`. - - @note Objects of this type are used with - @ref beast::websocket::stream::set_option. - - @par Example - Setting the maximum read message size. - @code - ... - websocket::stream ws(ios); - ws.set_option(read_message_max{65536}); - @endcode -*/ -#if GENERATING_DOCS -using read_message_max = implementation_defined; -#else -struct read_message_max -{ - std::size_t value; - - explicit - read_message_max(std::size_t n) - : value(n) - { - } -}; -#endif - -/** Write buffer size option. - - Sets the size of the write buffer used by the implementation to - send frames. The write buffer is needed when masking payload data - in the client role, compressing frames, or auto-fragmenting message - data. - - Lowering the size of the buffer can decrease the memory requirements - for each connection, while increasing the size of the buffer can reduce - the number of calls made to the next layer to write data. - - The default setting is 4096. The minimum value is 8. - - The write buffer size can only be changed when the stream is not - open. Undefined behavior results if the option is modified after a - successful WebSocket handshake. - - @note Objects of this type are used with - @ref beast::websocket::stream::set_option. - - @par Example - Setting the write buffer size. - @code - ... - websocket::stream ws(ios); - ws.set_option(write_buffer_size{8192}); - @endcode -*/ -#if GENERATING_DOCS -using write_buffer_size = implementation_defined; -#else -struct write_buffer_size -{ - std::size_t value; - - explicit - write_buffer_size(std::size_t n) - : value(n) - { - if(n < 8) - throw beast::detail::make_exception( - "write buffer size is too small", __FILE__, __LINE__); - } -}; -#endif - } // websocket } // beast diff --git a/include/beast/websocket/rfc6455.hpp b/include/beast/websocket/rfc6455.hpp index 3acb11e53d..bb819c8749 100644 --- a/include/beast/websocket/rfc6455.hpp +++ b/include/beast/websocket/rfc6455.hpp @@ -10,83 +10,138 @@ #include #include -#include +#include +#include #include #include namespace beast { namespace websocket { -/** WebSocket frame header opcodes. */ -enum class opcode : std::uint8_t -{ - cont = 0, - text = 1, - binary = 2, - rsv3 = 3, - rsv4 = 4, - rsv5 = 5, - rsv6 = 6, - rsv7 = 7, - close = 8, - ping = 9, - pong = 10, - crsvb = 11, - crsvc = 12, - crsvd = 13, - crsve = 14, - crsvf = 15 -}; +/** Returns `true` if the specified HTTP request is a WebSocket Upgrade. + + This function returns `true` when the passed HTTP Request + indicates a WebSocket Upgrade. It does not validate the + contents of the fields: it just trivially accepts requests + which could only possibly be a valid or invalid WebSocket + Upgrade message. + + Callers who wish to manually read HTTP requests in their + server implementation can use this function to determine if + the request should be routed to an instance of + @ref websocket::stream. + + @par Example + @code + void handle_connection(boost::asio::ip::tcp::socket& sock) + { + beast::flat_buffer buffer; + beast::http::request req; + beast::http::read(sock, buffer, req); + if(beast::websocket::is_upgrade(req)) + { + beast::websocket::stream ws{std::move(sock)}; + ws.accept(req); + } + } + @endcode + + @param req The HTTP Request object to check. + + @return `true` if the request is a WebSocket Upgrade. +*/ +template +bool +is_upgrade(beast::http::header> const& req); /** Close status codes. These codes accompany close frames. @see RFC 6455 7.4.1 Defined Status Codes - */ -#if GENERATING_DOCS -enum close_code -#else -namespace close_code { -using value = std::uint16_t; -enum -#endif +enum close_code : std::uint16_t { - /// used internally to mean "no error" - none = 0, - + /// Normal closure; the connection successfully completed whatever purpose for which it was created. normal = 1000, + + /// The endpoint is going away, either because of a server failure or because the browser is navigating away from the page that opened the connection. going_away = 1001, + + /// The endpoint is terminating the connection due to a protocol error. protocol_error = 1002, + /// The connection is being terminated because the endpoint received data of a type it cannot accept (for example, a text-only endpoint received binary data). unknown_data = 1003, - /// Indicates a received close frame has no close code - //no_code = 1005, // TODO - - /// Indicates the connection was closed without receiving a close frame - no_close = 1006, - + /// The endpoint is terminating the connection because a message was received that contained inconsistent data (e.g., non-UTF-8 data within a text message). bad_payload = 1007, + + /// The endpoint is terminating the connection because it received a message that violates its policy. This is a generic status code, used when codes 1003 and 1009 are not suitable. policy_error = 1008, + + /// The endpoint is terminating the connection because a data frame was received that is too large. too_big = 1009, + + /// The client is terminating the connection because it expected the server to negotiate one or more extension, but the server didn't. needs_extension = 1010, + + /// The server is terminating the connection because it encountered an unexpected condition that prevented it from fulfilling the request. internal_error = 1011, + /// The server is terminating the connection because it is restarting. service_restart = 1012, + + /// The server is terminating the connection due to a temporary condition, e.g. it is overloaded and is casting off some of its clients. try_again_later = 1013, - reserved1 = 1004, - no_status = 1005, // illegal on wire - abnormal = 1006, // illegal on wire - reserved2 = 1015, + //---- + // + // The following are illegal on the wire + // - last = 5000 // satisfy warnings + /** Used internally to mean "no error" + + This code is reserved and may not be sent. + */ + none = 0, + + /** Reserved for future use by the WebSocket standard. + + This code is reserved and may not be sent. + */ + reserved1 = 1004, + + /** No status code was provided even though one was expected. + + This code is reserved and may not be sent. + */ + no_status = 1005, + + /** Connection was closed without receiving a close frame + + This code is reserved and may not be sent. + */ + abnormal = 1006, + + /** Reserved for future use by the WebSocket standard. + + This code is reserved and may not be sent. + */ + reserved2 = 1014, + + /** Reserved for future use by the WebSocket standard. + + This code is reserved and may not be sent. + */ + reserved3 = 1015 + + // + //---- + + //last = 5000 // satisfy warnings }; -#if ! GENERATING_DOCS -} // close_code -#endif /// The type representing the reason string in a close frame. using reason_string = static_string<123, char>; @@ -102,7 +157,7 @@ using ping_data = static_string<125, char>; struct close_reason { /// The close code. - close_code::value code = close_code::none; + std::uint16_t code = close_code::none; /// The optional utf8-encoded reason string. reason_string reason; @@ -115,25 +170,29 @@ struct close_reason close_reason() = default; /// Construct from a code. - close_reason(close_code::value code_) + close_reason(std::uint16_t code_) : code(code_) { } - /// Construct from a reason. code is close_code::normal. - template - close_reason(char const (&reason_)[N]) + /// Construct from a reason string. code is @ref close_code::normal. + close_reason(string_view s) : code(close_code::normal) - , reason(reason_) + , reason(s) { } - /// Construct from a code and reason. - template - close_reason(close_code::value code_, - char const (&reason_)[N]) + /// Construct from a reason string literal. code is @ref close_code::normal. + close_reason(char const* s) + : code(close_code::normal) + , reason(s) + { + } + + /// Construct from a close code and reason string. + close_reason(close_code code_, string_view s) : code(code_) - , reason(reason_) + , reason(s) { } @@ -147,4 +206,6 @@ struct close_reason } // websocket } // beast +#include + #endif diff --git a/include/beast/websocket/ssl.hpp b/include/beast/websocket/ssl.hpp index d10dafdf44..ca4f039c9b 100644 --- a/include/beast/websocket/ssl.hpp +++ b/include/beast/websocket/ssl.hpp @@ -12,7 +12,6 @@ #include #include #include -#include namespace beast { namespace websocket { diff --git a/include/beast/websocket/stream.hpp b/include/beast/websocket/stream.hpp index 5f58042af4..4c494890c0 100644 --- a/include/beast/websocket/stream.hpp +++ b/include/beast/websocket/stream.hpp @@ -9,34 +9,60 @@ #define BEAST_WEBSOCKET_STREAM_HPP #include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include -#include #include #include +#include #include +#include namespace beast { namespace websocket { -/** Information about a WebSocket frame. +namespace detail { +class frame_test; +} - This information is provided to callers during frame - read operations. +/// The type of object holding HTTP Upgrade requests +using request_type = http::request; + +/// The type of object holding HTTP Upgrade responses +using response_type = http::response; + +/** The type of received control frame. + + Values of this type are passed to the control frame + callback set using @ref stream::control_callback. */ -struct frame_info +enum class frame_type { - /// Indicates the type of message (binary or text). - opcode op; + /// A close frame was received + close, - /// `true` if this is the last frame in the current message. - bool fin; + /// A ping frame was received + ping, + + /// A pong frame was received + pong }; //-------------------------------------------------------------------- @@ -46,12 +72,14 @@ struct frame_info The @ref stream class template provides asynchronous and blocking message-oriented functionality necessary for clients and servers to utilize the WebSocket protocol. + + For asynchronous operations, the application must ensure + that they are are all performed within the same implicit + or explicit strand. @par Thread Safety @e Distinct @e objects: Safe.@n - @e Shared @e objects: Unsafe. The application must ensure that - all asynchronous operations are performed within the same - implicit or explicit strand. + @e Shared @e objects: Unsafe. @par Example @@ -59,35 +87,190 @@ struct frame_info you would write: @code - websocket::stream ws(io_service); + websocket::stream ws{io_service}; @endcode Alternatively, you can write: @code - ip::tcp::socket sock(io_service); - websocket::stream ws(sock); + ip::tcp::socket sock{io_service}; + websocket::stream ws{sock}; @endcode @tparam NextLayer The type representing the next layer, to which data will be read and written during operations. For synchronous - operations, the type must support the @b `SyncStream` concept. + operations, the type must support the @b SyncStream concept. For asynchronous operations, the type must support the - @b `AsyncStream` concept. + @b AsyncStream concept. @note A stream object must not be moved or destroyed while there are pending asynchronous operations associated with it. @par Concepts - @b `AsyncStream`, - @b `Decorator`, - @b `DynamicBuffer`, - @b `SyncStream` + @b AsyncStream, + @b DynamicBuffer, + @b SyncStream */ template -class stream : public detail::stream_base +class stream { + friend class detail::frame_test; friend class stream_test; - dynabuf_readstream stream_; + buffered_read_stream stream_; + + /// Identifies the role of a WebSockets stream. + enum class role_type + { + /// Stream is operating as a client. + client, + + /// Stream is operating as a server. + server + }; + + friend class frame_test; + + using control_cb_type = + std::function; + + struct op {}; + + detail::maskgen maskgen_; // source of mask keys + std::size_t rd_msg_max_ = + 16 * 1024 * 1024; // max message size + bool wr_autofrag_ = true; // auto fragment + std::size_t wr_buf_size_ = 4096; // write buffer size + std::size_t rd_buf_size_ = 4096; // read buffer size + detail::opcode wr_opcode_ = + detail::opcode::text; // outgoing message type + control_cb_type ctrl_cb_; // control callback + role_type role_; // server or client + bool failed_; // the connection failed + + bool wr_close_; // sent close frame + op* wr_block_; // op currenly writing + + ping_data* ping_data_; // where to put the payload + detail::pausation rd_op_; // paused read op + detail::pausation wr_op_; // paused write op + detail::pausation ping_op_; // paused ping op + detail::pausation close_op_; // paused close op + close_reason cr_; // set from received close frame + + // State information for the message being received + // + struct rd_t + { + // opcode of current message being read + detail::opcode op; + + // `true` if the next frame is a continuation. + bool cont; + + // Checks that test messages are valid utf8 + detail::utf8_checker utf8; + + // Size of the current message so far. + std::uint64_t size; + + // Size of the read buffer. + // This gets set to the read buffer size option at the + // beginning of sending a message, so that the option can be + // changed mid-send without affecting the current message. + std::size_t buf_size; + + // The read buffer. Used for compression and masking. + std::unique_ptr buf; + }; + + rd_t rd_; + + // State information for the message being sent + // + struct wr_t + { + // `true` if next frame is a continuation, + // `false` if next frame starts a new message + bool cont; + + // `true` if this message should be auto-fragmented + // This gets set to the auto-fragment option at the beginning + // of sending a message, so that the option can be changed + // mid-send without affecting the current message. + bool autofrag; + + // `true` if this message should be compressed. + // This gets set to the compress option at the beginning of + // of sending a message, so that the option can be changed + // mid-send without affecting the current message. + bool compress; + + // Size of the write buffer. + // This gets set to the write buffer size option at the + // beginning of sending a message, so that the option can be + // changed mid-send without affecting the current message. + std::size_t buf_size; + + // The write buffer. Used for compression and masking. + // The buffer is allocated or reallocated at the beginning of + // sending a message. + std::unique_ptr buf; + }; + + wr_t wr_; + + // State information for the permessage-deflate extension + struct pmd_t + { + // `true` if current read message is compressed + bool rd_set; + + zlib::deflate_stream zo; + zlib::inflate_stream zi; + }; + + // If not engaged, then permessage-deflate is not + // enabled for the currently active session. + std::unique_ptr pmd_; + + // Local options for permessage-deflate + permessage_deflate pmd_opts_; + + // Offer for clients, negotiated result for servers + detail::pmd_offer pmd_config_; + + void + open(role_type role); + + void + close(); + + template + std::size_t + read_fh1(detail::frame_header& fh, + DynamicBuffer& db, close_code& code); + + template + void + read_fh2(detail::frame_header& fh, + DynamicBuffer& db, close_code& code); + + // Called before receiving the first frame of each message + void + rd_begin(); + + // Called before sending the first frame of each message + // + void + wr_begin(); + + template + void + write_close(DynamicBuffer& db, close_reason const& rc); + + template + void + write_ping(DynamicBuffer& db, + detail::opcode op, ping_data const& data); public: /// The type of the next layer. @@ -96,14 +279,9 @@ public: /// The type of the lowest layer. using lowest_layer_type = - #if GENERATING_DOCS - implementation_defined; - #else - typename beast::detail::get_lowest_layer< - next_layer_type>::type; - #endif + typename get_lowest_layer::type; - /** Move-construct a stream. + /** Move constructor If @c NextLayer is move constructible, this function will move-construct a new stream from the existing stream. @@ -113,17 +291,17 @@ public: */ stream(stream&&) = default; - /** Move assignment. + /** Move assignment - If `NextLayer` is move constructible, this function - will move-construct a new stream from the existing stream. + If `NextLayer` is move assignable, this function + will move-assign a new stream from the existing stream. @note The behavior of move assignment on or from streams with active or pending operations is undefined. */ stream& operator=(stream&&) = default; - /** Construct a WebSocket stream. + /** Constructor This constructor creates a websocket stream and initializes the next layer object. @@ -138,133 +316,21 @@ public: explicit stream(Args&&... args); - /** Destructor. + /** Destructor @note A stream object must not be destroyed while there are pending asynchronous operations associated with it. */ ~stream() = default; - /** Set options on the stream. + /** Return the `io_service` associated with the stream - The application must ensure that calls to set options - are performed within the same implicit or explicit strand. - - @param args One or more stream options to set. - */ -#if GENERATING_DOCS - template - void - set_option(Args&&... args) -#else - template - void - set_option(A1&& a1, A2&& a2, An&&... an) -#endif - { - set_option(std::forward(a1)); - set_option(std::forward(a2), - std::forward(an)...); - } - - /// Set the automatic fragment size option - void - set_option(auto_fragment const& o) - { - wr_autofrag_ = o.value; - } - - /** Set the decorator used for HTTP messages. - - The value for this option is a callable type with two - optional signatures: - - @code - void(request_type&); - - void(response_type&); - @endcode - - If a matching signature is provided, the callable type - will be invoked with the HTTP request or HTTP response - object as appropriate. When a signature is omitted, - a default consisting of the string Beast followed by - the version number is used. - */ - void -#if GENERATING_DOCS - set_option(implementation_defined o) -#else - set_option(detail::decorator_type const& o) -#endif - { - d_ = o; - } - - /// Set the keep-alive option - void - set_option(keep_alive const& o) - { - keep_alive_ = o.value; - } - - /// Set the outgoing message type - void - set_option(message_type const& o) - { - wr_opcode_ = o.value; - } - - /// Set the permessage-deflate extension options - void - set_option(permessage_deflate const& o); - - /// Get the permessage-deflate extension options - void - get_option(permessage_deflate& o) - { - o = pmd_opts_; - } - - /// Set the ping callback - void - set_option(ping_callback o) - { - ping_cb_ = std::move(o.value); - } - - /// Set the read buffer size - void - set_option(read_buffer_size const& o) - { - rd_buf_size_ = o.value; - // VFALCO What was the thinking here? - //stream_.capacity(o.value); - } - - /// Set the maximum incoming message size allowed - void - set_option(read_message_max const& o) - { - rd_msg_max_ = o.value; - } - - /// Set the size of the write buffer - void - set_option(write_buffer_size const& o) - { - wr_buf_size_ = o.value; - } - - /** Get the io_service associated with the stream. - - This function may be used to obtain the io_service object + This function may be used to obtain the `io_service` object that the stream uses to dispatch handlers for asynchronous operations. @return A reference to the io_service object that the stream - will use to dispatch handlers. Ownership is not transferred - to the caller. + will use to dispatch handlers. */ boost::asio::io_service& get_io_service() @@ -272,13 +338,13 @@ public: return stream_.get_io_service(); } - /** Get a reference to the next layer. + /** Get a reference to the next layer This function returns a reference to the next layer in a stack of stream layers. @return A reference to the next layer in the stack of - stream layers. Ownership is not transferred to the caller. + stream layers. */ next_layer_type& next_layer() @@ -286,13 +352,13 @@ public: return stream_.next_layer(); } - /** Get a reference to the next layer. + /** Get a reference to the next layer This function returns a reference to the next layer in a stack of stream layers. @return A reference to the next layer in the stack of - stream layers. Ownership is not transferred to the caller. + stream layers. */ next_layer_type const& next_layer() const @@ -300,13 +366,13 @@ public: return stream_.next_layer(); } - /** Get a reference to the lowest layer. + /** Get a reference to the lowest layer This function returns a reference to the lowest layer in a stack of stream layers. @return A reference to the lowest layer in the stack of - stream layers. Ownership is not transferred to the caller. + stream layers. */ lowest_layer_type& lowest_layer() @@ -314,7 +380,7 @@ public: return stream_.lowest_layer(); } - /** Get a reference to the lowest layer. + /** Get a reference to the lowest layer This function returns a reference to the lowest layer in a stack of stream layers. @@ -328,6 +394,272 @@ public: return stream_.lowest_layer(); } + /// Set the permessage-deflate extension options + void + set_option(permessage_deflate const& o); + + /// Get the permessage-deflate extension options + void + get_option(permessage_deflate& o) + { + o = pmd_opts_; + } + + /** Set the automatic fragmentation option. + + Determines if outgoing message payloads are broken up into + multiple pieces. + + When the automatic fragmentation size is turned on, outgoing + message payloads are broken up into multiple frames no larger + than the write buffer size. + + The default setting is to fragment messages. + + @param v A `bool` indicating if auto fragmentation should be on. + + @par Example + Setting the automatic fragmentation option: + @code + ws.auto_fragment(true); + @endcode + */ + void + auto_fragment(bool v) + { + wr_autofrag_ = v; + } + + /// Returns `true` if the automatic fragmentation option is set. + bool + auto_fragment() const + { + return wr_autofrag_; + } + + /** Set the binary message option. + + This controls whether or not outgoing message opcodes + are set to binary or text. The setting is only applied + at the start when a caller begins a new message. Changing + the opcode after a message is started will only take effect + after the current message being sent is complete. + + The default setting is to send text messages. + + @param v `true` if outgoing messages should indicate + binary, or `false` if they should indicate text. + + @par Example + Setting the message type to binary. + @code + ws.binary(true); + @endcode + */ + void + binary(bool v) + { + wr_opcode_ = v ? + detail::opcode::binary : + detail::opcode::text; + } + + /// Returns `true` if the binary message option is set. + bool + binary() const + { + return wr_opcode_ == detail::opcode::binary; + } + + /** Set the control frame callback. + + Sets the callback to be invoked whenever a ping, pong, + or close control frame is received during a call to one + of the following functions: + + @li @ref beast::websocket::stream::read + @li @ref beast::websocket::stream::read_frame + @li @ref beast::websocket::stream::async_read + @li @ref beast::websocket::stream::async_read_frame + + Unlike completion handlers, the callback will be invoked + for each control frame during a call to any synchronous + or asynchronous read function. The operation is passive, + with no associated error code, and triggered by reads. + + The signature of the callback must be: + @code + void + callback( + frame_type kind, // The type of frame + string_view payload // The payload in the frame + ); + @endcode + + For close frames, the close reason code may be obtained by + calling the function @ref reason. + + If the read operation which receives the control frame is + an asynchronous operation, the callback will be invoked using + the same method as that used to invoke the final handler. + + @note It is not necessary to send a close frame upon receipt + of a close frame. The implementation does this automatically. + Attempting to send a close frame after a close frame is + received will result in undefined behavior. + + @param cb The callback to set. + */ + void + control_callback( + std::function cb) + { + ctrl_cb_ = std::move(cb); + } + + /** Set the read buffer size option. + + Sets the size of the read buffer used by the implementation to + receive frames. The read buffer is needed when permessage-deflate + is used. + + Lowering the size of the buffer can decrease the memory requirements + for each connection, while increasing the size of the buffer can reduce + the number of calls made to the next layer to read data. + + The default setting is 4096. The minimum value is 8. + + @param n The size of the read buffer. + + @throws std::invalid_argument If the buffer size is less than 8. + + @par Example + Setting the read buffer size. + @code + ws.read_buffer_size(16 * 1024); + @endcode + */ + void + read_buffer_size(std::size_t n) + { + if(n < 8) + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "read buffer size underflow"}); + rd_buf_size_ = n; + } + + /// Returns the read buffer size setting. + std::size_t + read_buffer_size() const + { + return rd_buf_size_; + } + + /** Set the maximum incoming message size option. + + Sets the largest permissible incoming message size. Message + frame fields indicating a size that would bring the total + message size over this limit will cause a protocol failure. + + The default setting is 16 megabytes. A value of zero indicates + a limit of the maximum value of a `std::uint64_t`. + + @par Example + Setting the maximum read message size. + @code + ws.read_message_max(65536); + @endcode + + @param n The limit on the size of incoming messages. + */ + void + read_message_max(std::size_t n) + { + rd_msg_max_ = n; + } + + /// Returns the maximum incoming message size setting. + std::size_t + read_message_max() const + { + return rd_msg_max_; + } + + /** Set the write buffer size option. + + Sets the size of the write buffer used by the implementation to + send frames. The write buffer is needed when masking payload data + in the client role, compressing frames, or auto-fragmenting message + data. + + Lowering the size of the buffer can decrease the memory requirements + for each connection, while increasing the size of the buffer can reduce + the number of calls made to the next layer to write data. + + The default setting is 4096. The minimum value is 8. + + The write buffer size can only be changed when the stream is not + open. Undefined behavior results if the option is modified after a + successful WebSocket handshake. + + @par Example + Setting the write buffer size. + @code + ws.write_buffer_size(8192); + @endcode + + @param n The size of the write buffer in bytes. + */ + void + write_buffer_size(std::size_t n) + { + if(n < 8) + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "write buffer size underflow"}); + wr_buf_size_ = n; + }; + + /// Returns the size of the write buffer. + std::size_t + write_buffer_size() const + { + return wr_buf_size_; + } + + /** Set the text message option. + + This controls whether or not outgoing message opcodes + are set to binary or text. The setting is only applied + at the start when a caller begins a new message. Changing + the opcode after a message is started will only take effect + after the current message being sent is complete. + + The default setting is to send text messages. + + @param v `true` if outgoing messages should indicate + text, or `false` if they should indicate binary. + + @par Example + Setting the message type to text. + @code + ws.text(true); + @endcode + */ + void + text(bool v) + { + wr_opcode_ = v ? + detail::opcode::text : + detail::opcode::binary; + } + + /// Returns `true` if the text message option is set. + bool + text() const + { + return wr_opcode_ == detail::opcode::text; + } + /** Returns the close reason received from the peer. This is only valid after a read completes with error::closed. @@ -338,27 +670,56 @@ public: return cr_; } + /** Returns `true` if the latest message data indicates binary. + + This function informs the caller of whether the last + received message frame represents a message with the + binary opcode. + + If there is no last message frame, the return value is + undefined. + */ + bool + got_binary() + { + return rd_.op == detail::opcode::binary; + } + + /** Returns `true` if the latest message data indicates text. + + This function informs the caller of whether the last + received message frame represents a message with the + text opcode. + + If there is no last message frame, the return value is + undefined. + */ + bool + got_text() + { + return ! got_binary(); + } + /** Read and respond to a WebSocket HTTP Upgrade request. - This function is used to synchronously read a HTTP WebSocket - Upgrade request and send the HTTP response. The call blocks until - one of the following conditions is true: + This function is used to synchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The call blocks + until one of the following conditions is true: - @li A HTTP request finishes receiving, and a HTTP response finishes - sending. + @li The request is received and the response finishes sending. @li An error occurs on the stream. - This function is implemented in terms of one or more calls to the - next layer's `read_some` and `write_some` functions. + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. - If the stream receives a valid HTTP WebSocket Upgrade request, a - HTTP response is sent back indicating a successful upgrade. When this - call returns, the stream is then ready to send and receive WebSocket - protocol frames and messages. + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. - If the HTTP Upgrade request is invalid or cannot be satisfied, a - HTTP response is sent indicating the reason and status code + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code (typically 400, "Bad Request"). This counts as a failure. @throws system_error Thrown on failure. @@ -368,25 +729,61 @@ public: /** Read and respond to a WebSocket HTTP Upgrade request. - This function is used to synchronously read a HTTP WebSocket - Upgrade request and send the HTTP response. The call blocks until - one of the following conditions is true: + This function is used to synchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The call blocks + until one of the following conditions is true: - @li A HTTP request finishes receiving, and a HTTP response finishes - sending. + @li The request is received and the response finishes sending. @li An error occurs on the stream. - This function is implemented in terms of one or more calls to the - next layer's `read_some` and `write_some` functions. + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. - If the stream receives a valid HTTP WebSocket Upgrade request, a - HTTP response is sent back indicating a successful upgrade. When this - call returns, the stream is then ready to send and receive WebSocket - protocol frames and messages. + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. - If the HTTP Upgrade request is invalid or cannot be satisfied, a - HTTP response is sent indicating the reason and status code + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param decorator A function object which will be called to modify + the HTTP response object delivered by the implementation. This + could be used to set the Server field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + response_type& res + ); @endcode + + @throws system_error Thrown on failure. + */ + template + void + accept_ex(ResponseDecorator const& decorator); + + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The call blocks + until one of the following conditions is true: + + @li The request is received and the response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code (typically 400, "Bad Request"). This counts as a failure. @param ec Set to indicate what error occurred, if any. @@ -394,38 +791,578 @@ public: void accept(error_code& ec); + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The call blocks + until one of the following conditions is true: + + @li The request is received and the response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param decorator A function object which will be called to modify + the HTTP response object delivered by the implementation. This + could be used to set the Server field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + response_type& res + ); @endcode + + @param ec Set to indicate what error occurred, if any. + */ + template + void + accept_ex(ResponseDecorator const& decorator, + error_code& ec); + + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The call blocks + until one of the following conditions is true: + + @li The request is received and the response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param buffers Caller provided data that has already been + received on the stream. The implementation will copy the + caller provided data before the function returns. + + @throws system_error Thrown on failure. + */ + template +#if BEAST_DOXYGEN + void +#else + typename std::enable_if::value>::type +#endif + accept(ConstBufferSequence const& buffers); + + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The call blocks + until one of the following conditions is true: + + @li The request is received and the response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param buffers Caller provided data that has already been + received on the stream. The implementation will copy the + caller provided data before the function returns. + + @param decorator A function object which will be called to modify + the HTTP response object delivered by the implementation. This + could be used to set the Server field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + response_type& res + ); @endcode + + @throws system_error Thrown on failure. + */ + template +#if BEAST_DOXYGEN + void +#else + typename std::enable_if::value>::type +#endif + accept_ex(ConstBufferSequence const& buffers, + ResponseDecorator const& decorator); + + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The call blocks + until one of the following conditions is true: + + @li The request is received and the response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param buffers Caller provided data that has already been + received on the stream. The implementation will copy the + caller provided data before the function returns. + + @param ec Set to indicate what error occurred, if any. + */ + template +#if BEAST_DOXYGEN + void +#else + typename std::enable_if::value>::type +#endif + accept(ConstBufferSequence const& buffers, error_code& ec); + + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The call blocks + until one of the following conditions is true: + + @li The request is received and the response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param buffers Caller provided data that has already been + received on the stream. The implementation will copy the + caller provided data before the function returns. + + @param decorator A function object which will be called to modify + the HTTP response object delivered by the implementation. This + could be used to set the Server field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + response_type& res + ); @endcode + + @param ec Set to indicate what error occurred, if any. + */ + template +#if BEAST_DOXYGEN + void +#else + typename std::enable_if::value>::type +#endif + accept_ex(ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, + error_code& ec); + + /** Respond to a WebSocket HTTP Upgrade request + + This function is used to synchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade. + The call blocks until one of the following conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not + access this object from other threads. + + @throws system_error Thrown on failure. + */ + template + void + accept(http::header> const& req); + + /** Respond to a WebSocket HTTP Upgrade request + + This function is used to synchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade. + The call blocks until one of the following conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not + access this object from other threads. + + @param decorator A function object which will be called to modify + the HTTP response object delivered by the implementation. This + could be used to set the Server field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + response_type& res + ); @endcode + + @throws system_error Thrown on failure. + */ + template + void + accept_ex(http::header> const& req, + ResponseDecorator const& decorator); + + /** Respond to a WebSocket HTTP Upgrade request + + This function is used to synchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade. + The call blocks until one of the following conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not + access this object from other threads. + + @param ec Set to indicate what error occurred, if any. + */ + template + void + accept(http::header> const& req, + error_code& ec); + + /** Respond to a WebSocket HTTP Upgrade request + + This function is used to synchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade. + The call blocks until one of the following conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not + access this object from other threads. + + @param decorator A function object which will be called to modify + the HTTP response object delivered by the implementation. This + could be used to set the Server field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + response_type& res + ); @endcode + + @param ec Set to indicate what error occurred, if any. + */ + template + void + accept_ex(http::header> const& req, + ResponseDecorator const& decorator, + error_code& ec); + + /** Respond to a WebSocket HTTP Upgrade request + + This function is used to synchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade. + The call blocks until one of the following conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not + access this object from other threads. + + @param buffers Caller provided data that has already been + received on the stream. This must not include the octets + corresponding to the HTTP Upgrade request. The implementation + will copy the caller provided data before the function returns. + + @throws system_error Thrown on failure. + */ + template + void + accept(http::header> const& req, + ConstBufferSequence const& buffers); + + /** Respond to a WebSocket HTTP Upgrade request + + This function is used to synchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade. + The call blocks until one of the following conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not + access this object from other threads. + + @param buffers Caller provided data that has already been + received on the stream. This must not include the octets + corresponding to the HTTP Upgrade request. The implementation + will copy the caller provided data before the function returns. + + @param decorator A function object which will be called to modify + the HTTP response object delivered by the implementation. This + could be used to set the Server field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + response_type& res + ); @endcode + + @throws system_error Thrown on failure. + */ + template + void + accept_ex(http::header> const& req, + ConstBufferSequence const& buffers, + ResponseDecorator const& decorator); + + /** Respond to a WebSocket HTTP Upgrade request + + This function is used to synchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade. + The call blocks until one of the following conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not + access this object from other threads. + + @param buffers Caller provided data that has already been + received on the stream. This must not include the octets + corresponding to the HTTP Upgrade request. The implementation + will copy the caller provided data before the function returns. + + @param ec Set to indicate what error occurred, if any. + */ + template + void + accept(http::header const& req, + ConstBufferSequence const& buffers, error_code& ec); + + /** Respond to a WebSocket HTTP Upgrade request + + This function is used to synchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade. + The call blocks until one of the following conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to + the next layer's `read_some` and `write_some` functions. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When this call returns, the stream is then ready to send and + receive WebSocket protocol frames and messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not + access this object from other threads. + + @param buffers Caller provided data that has already been + received on the stream. This must not include the octets + corresponding to the HTTP Upgrade request. The implementation + will copy the caller provided data before the function returns. + + @param decorator A function object which will be called to modify + the HTTP response object delivered by the implementation. This + could be used to set the Server field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + response_type& res + ); @endcode + + @param ec Set to indicate what error occurred, if any. + */ + template + void + accept_ex(http::header> const& req, + ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, + error_code& ec); + /** Start reading and responding to a WebSocket HTTP Upgrade request. - This function is used to asynchronously read a HTTP WebSocket + This function is used to asynchronously read an HTTP WebSocket Upgrade request and send the HTTP response. The function call always returns immediately. The asynchronous operation will continue until one of the following conditions is true: - @li A HTTP request finishes receiving, and a HTTP response finishes - sending. + @li The request is received and the response finishes sending. @li An error occurs on the stream. - This operation is implemented in terms of one or more calls to the - next layer's `async_read_some` and `async_write_some` functions, and - is known as a composed operation. The program must ensure - that the stream performs no other operations until this operation - completes. + This operation is implemented in terms of one or more calls to + the next layer's `async_read_some` and `async_write_some` + functions, and is known as a composed operation. The + program must ensure that the stream performs no other + asynchronous operations until this operation completes. - If the stream receives a valid HTTP WebSocket Upgrade request, a - HTTP response is sent back indicating a successful upgrade. When - this call returns, the stream is then ready to send and receive - WebSocket protocol frames and messages. + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When the completion handler is invoked, the stream is then + ready to send and receive WebSocket protocol frames and + messages. - If the HTTP Upgrade request is invalid or cannot be satisfied, a - HTTP response is sent indicating the reason and status code - (typically 400, "Bad Request"). This counts as a failure. + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure, and + the completion handler will be invoked with a suitable error + code set. - @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: + @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 // result of operation + error_code const& ec // Result of operation ); @endcode Regardless of whether the asynchronous operation completes immediately or not, the handler will not be invoked from within @@ -433,112 +1370,101 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion< - AcceptHandler, void(error_code)>::result_type + async_return_type< + AcceptHandler, void(error_code)> #endif async_accept(AcceptHandler&& handler); - /** Read and respond to a WebSocket HTTP Upgrade request. - - This function is used to synchronously read a HTTP WebSocket - Upgrade request and send the HTTP response. The call blocks until - one of the following conditions is true: - - @li A HTTP request finishes receiving, and a HTTP response finishes - sending. - - @li An error occurs on the stream. - - This function is implemented in terms of one or more calls to the - next layer's `read_some` and `write_some` functions. - - If the stream receives a valid HTTP WebSocket Upgrade request, a - HTTP response is sent back indicating a successful upgrade. When - this call returns, the stream is then ready to send and receive - WebSocket protocol frames and messages. - - If the HTTP Upgrade request is invalid or cannot be satisfied, a - HTTP response is sent indicating the reason and status code - (typically 400, "Bad Request"). This counts as a failure. - - @param buffers Caller provided data that has already been - received on the stream. This may be used for implementations - allowing multiple protocols on the same stream. The - buffered data will first be applied to the handshake, and - then to received WebSocket frames. The implementation will - copy the caller provided data before the function returns. - - @throws system_error Thrown on failure. - */ - template - void - accept(ConstBufferSequence const& buffers); - - /** Read and respond to a WebSocket HTTP Upgrade request. - - This function is used to synchronously read a HTTP WebSocket - Upgrade request and send the HTTP response. The call blocks until - one of the following conditions is true: - - @li A HTTP request finishes receiving, and a HTTP response finishes - sending. - - @li An error occurs on the stream. - - This function is implemented in terms of one or more calls to the - next layer's `read_some` and `write_some` functions. - - If the stream receives a valid HTTP WebSocket Upgrade request, a - HTTP response is sent back indicating a successful upgrade. When - this call returns, the stream is then ready to send and receive - WebSocket protocol frames and messages. - - If the HTTP Upgrade request is invalid or cannot be satisfied, a - HTTP response is sent indicating the reason and status code - (typically 400, "Bad Request"). This counts as a failure. - - @param buffers Caller provided data that has already been - received on the stream. This may be used for implementations - allowing multiple protocols on the same stream. The - buffered data will first be applied to the handshake, and - then to received WebSocket frames. The implementation will - copy the caller provided data before the function returns. - - @param ec Set to indicate what error occurred, if any. - */ - template - void - accept(ConstBufferSequence const& buffers, error_code& ec); - /** Start reading and responding to a WebSocket HTTP Upgrade request. - This function is used to asynchronously read a HTTP WebSocket + This function is used to asynchronously read an HTTP WebSocket Upgrade request and send the HTTP response. The function call always returns immediately. The asynchronous operation will continue until one of the following conditions is true: - @li A HTTP request finishes receiving, and a HTTP response finishes - sending. + @li The request is received and the response finishes sending. @li An error occurs on the stream. - This operation is implemented in terms of one or more calls to the - next layer's `async_read_some` and `async_write_some` functions, and - is known as a composed operation. The program must ensure - that the stream performs no other operations until this operation - completes. + This operation is implemented in terms of one or more calls to + the next layer's `async_read_some` and `async_write_some` + functions, and is known as a composed operation. The + program must ensure that the stream performs no other + asynchronous operations until this operation completes. - If the stream receives a valid HTTP WebSocket Upgrade request, a - HTTP response is sent back indicating a successful upgrade. When - this call returns, the stream is then ready to send and receive - WebSocket protocol frames and messages. + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When the completion handler is invoked, the stream is then + ready to send and receive WebSocket protocol frames and + messages. - If the HTTP Upgrade request is invalid or cannot be satisfied, a - HTTP response is sent indicating the reason and status code - (typically 400, "Bad Request"). This counts as a failure. + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure, and + the completion handler will be invoked with a suitable error + code set. + + @param decorator A function object which will be called to modify + the HTTP response object delivered by the implementation. This + could be used to set the Server field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + response_type& res + ); @endcode + + @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& 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 +#if BEAST_DOXYGEN + void_or_deduced +#else + async_return_type< + AcceptHandler, void(error_code)> +#endif + async_accept_ex(ResponseDecorator const& decorator, + AcceptHandler&& handler); + + /** Start reading and responding to a WebSocket HTTP Upgrade request. + + This function is used to asynchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The function call + always returns immediately. The asynchronous operation will + continue until one of the following conditions is true: + + @li The request is received and the response finishes sending. + + @li An error occurs on the stream. + + This operation is implemented in terms of one or more calls to + the next layer's `async_read_some` and `async_write_some` + functions, and is known as a composed operation. The + program must ensure that the stream performs no other + asynchronous operations until this operation completes. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When the completion handler is invoked, the stream is then + ready to send and receive WebSocket protocol frames and + messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure, and + the completion handler will be invoked with a suitable error + code set. @param buffers Caller provided data that has already been received on the stream. This may be used for implementations @@ -547,11 +1473,11 @@ public: then to received WebSocket frames. The implementation will copy the caller provided data before the function returns. - @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: + @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 // result of operation + error_code const& ec // Result of operation ); @endcode Regardless of whether the asynchronous operation completes immediately or not, the handler will not be invoked from within @@ -559,140 +1485,355 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion< - AcceptHandler, void(error_code)>::result_type + typename std::enable_if< + ! http::detail::is_header::value, + async_return_type>::type #endif async_accept(ConstBufferSequence const& buffers, AcceptHandler&& handler); - /** Respond to a WebSocket HTTP Upgrade request + /** Start reading and responding to a WebSocket HTTP Upgrade request. - This function is used to synchronously send the HTTP response to - a HTTP request possibly containing a WebSocket Upgrade request. - The call blocks until one of the following conditions is true: + This function is used to asynchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The function call + always returns immediately. The asynchronous operation will + continue until one of the following conditions is true: - @li A HTTP response finishes sending. + @li The request is received and the response finishes sending. @li An error occurs on the stream. - This function is implemented in terms of one or more calls to the - next layer's `write_some` functions. + This operation is implemented in terms of one or more calls to + the next layer's `async_read_some` and `async_write_some` + functions, and is known as a composed operation. The + program must ensure that the stream performs no other + asynchronous operations until this operation completes. - If the passed HTTP request is a valid HTTP WebSocket Upgrade - request, a HTTP response is sent back indicating a successful - upgrade. When this call returns, the stream is then ready to send - and receive WebSocket protocol frames and messages. + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When the completion handler is invoked, the stream is then + ready to send and receive WebSocket protocol frames and + messages. - If the HTTP request is invalid or cannot be satisfied, a HTTP - response is sent indicating the reason and status code (typically - 400, "Bad Request"). This counts as a failure. + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure, and + the completion handler will be invoked with a suitable error + code set. - @param request An object containing the HTTP Upgrade request. - Ownership is not transferred, the implementation will not access - this object from other threads. + @param buffers Caller provided data that has already been + received on the stream. This may be used for implementations + allowing multiple protocols on the same stream. The + buffered data will first be applied to the handshake, and + then to received WebSocket frames. The implementation will + copy the caller provided data before the function returns. - @throws system_error Thrown on failure. - */ - // VFALCO TODO This should also take a DynamicBuffer with any leftover bytes. - template - void - accept(http::request const& request); + @param decorator A function object which will be called to modify + the HTTP response object delivered by the implementation. This + could be used to set the Server field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + response_type& res + ); @endcode - /** Respond to a WebSocket HTTP Upgrade request - - This function is used to synchronously send the HTTP response to - a HTTP request possibly containing a WebSocket Upgrade request. - The call blocks until one of the following conditions is true: - - @li A HTTP response finishes sending. - - @li An error occurs on the stream. - - This function is implemented in terms of one or more calls to the - next layer's `write_some` functions. - - If the passed HTTP request is a valid HTTP WebSocket Upgrade - request, a HTTP response is sent back indicating a successful - upgrade. When this call returns, the stream is then ready to send - and receive WebSocket protocol frames and messages. - - If the HTTP request is invalid or cannot be satisfied, a HTTP - response is sent indicating the reason and status code (typically - 400, "Bad Request"). This counts as a failure. - - @param request An object containing the HTTP Upgrade request. - Ownership is not transferred, the implementation will not access - this object from other threads. - - @param ec Set to indicate what error occurred, if any. - */ - template - void - accept(http::request const& request, - error_code& ec); - - /** Start responding to a WebSocket HTTP Upgrade request. - - This function is used to asynchronously send the HTTP response - to a HTTP request possibly containing a WebSocket Upgrade request. - The function call always returns immediately. The asynchronous - operation will continue until one of the following conditions is - true: - - @li A HTTP response finishes sending. - - @li An error occurs on the stream. - - This operation is implemented in terms of one or more calls to the - next layer's `async_write_some` functions, and is known as a - composed operation. The program must ensure that the - stream performs no other operations until this operation completes. - - If the passed HTTP request is a valid HTTP WebSocket Upgrade - request, a HTTP response is sent back indicating a successful - upgrade. When this asynchronous operation completes, the stream is - then ready to send and receive WebSocket protocol frames and messages. - - If the HTTP request is invalid or cannot be satisfied, a HTTP - response is sent indicating the reason and status code (typically - 400, "Bad Request"). This counts as a failure. - - @param request An object containing the HTTP Upgrade request. - Ownership is not transferred, the implementation will not access - this object from other threads. - - @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: + @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 // result of operation + error_code const& 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 -#if GENERATING_DOCS + template +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion< - AcceptHandler, void(error_code)>::result_type + typename std::enable_if< + ! http::detail::is_header::value, + async_return_type>::type #endif - async_accept(http::request const& request, - AcceptHandler&& handler); + async_accept_ex(ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, + AcceptHandler&& handler); - /** Send a HTTP WebSocket Upgrade request and receive the response. + /** Start responding to a WebSocket HTTP Upgrade request. + + This function is used to asynchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade + request. The function call always returns immediately. The + asynchronous operation will continue until one of the following + conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This operation is implemented in terms of one or more calls to + the next layer's `async_write_some` functions, and is known as + a composed operation. The program must ensure that the + stream performs no other operations until this operation + completes. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When the completion handler is invoked, the stream is then + ready to send and receive WebSocket protocol frames and + messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure, and + the completion handler will be invoked with a suitable error + code set. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not access + this object from other threads. + + @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& 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 +#if BEAST_DOXYGEN + void_or_deduced +#else + async_return_type< + AcceptHandler, void(error_code)> +#endif + async_accept(http::header> const& req, + AcceptHandler&& handler); + + /** Start responding to a WebSocket HTTP Upgrade request. + + This function is used to asynchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade + request. The function call always returns immediately. The + asynchronous operation will continue until one of the following + conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This operation is implemented in terms of one or more calls to + the next layer's `async_write_some` functions, and is known as + a composed operation. The program must ensure that the + stream performs no other operations until this operation + completes. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When the completion handler is invoked, the stream is then + ready to send and receive WebSocket protocol frames and + messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure, and + the completion handler will be invoked with a suitable error + code set. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not access + this object from other threads. + + @param decorator A function object which will be called to modify + the HTTP response object delivered by the implementation. This + could be used to set the Server field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + response_type& res + ); @endcode + + @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& 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 +#if BEAST_DOXYGEN + void_or_deduced +#else + async_return_type< + AcceptHandler, void(error_code)> +#endif + async_accept_ex(http::header> const& req, + ResponseDecorator const& decorator, + AcceptHandler&& handler); + + /** Start responding to a WebSocket HTTP Upgrade request. + + This function is used to asynchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade + request. The function call always returns immediately. The + asynchronous operation will continue until one of the following + conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This operation is implemented in terms of one or more calls to + the next layer's `async_write_some` functions, and is known as + a composed operation. The program must ensure that the + stream performs no other operations until this operation + completes. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When the completion handler is invoked, the stream is then + ready to send and receive WebSocket protocol frames and + messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure, and + the completion handler will be invoked with a suitable error + code set. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not access + this object from other threads. + + @param buffers Caller provided data that has already been + received on the stream. This may be used for implementations + allowing multiple protocols on the same stream. The + buffered data will first be applied to the handshake, and + then to received WebSocket frames. The implementation will + copy the caller provided data before the function returns. + + @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& 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 +#if BEAST_DOXYGEN + void_or_deduced +#else + async_return_type< + AcceptHandler, void(error_code)> +#endif + async_accept(http::header> const& req, + ConstBufferSequence const& buffers, + AcceptHandler&& handler); + + /** Start responding to a WebSocket HTTP Upgrade request. + + This function is used to asynchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade + request. The function call always returns immediately. The + asynchronous operation will continue until one of the following + conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This operation is implemented in terms of one or more calls to + the next layer's `async_write_some` functions, and is known as + a composed operation. The program must ensure that the + stream performs no other operations until this operation + completes. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When the completion handler is invoked, the stream is then + ready to send and receive WebSocket protocol frames and + messages. + + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure, and + the completion handler will be invoked with a suitable error + code set. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not access + this object from other threads. + + @param buffers Caller provided data that has already been + received on the stream. This may be used for implementations + allowing multiple protocols on the same stream. The + buffered data will first be applied to the handshake, and + then to received WebSocket frames. The implementation will + copy the caller provided data before the function returns. + + @param decorator A function object which will be called to modify + the HTTP response object delivered by the implementation. This + could be used to set the Server field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + response_type& res + ); @endcode + + @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& 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 +#if BEAST_DOXYGEN + void_or_deduced +#else + async_return_type< + AcceptHandler, void(error_code)> +#endif + async_accept_ex(http::header> const& req, + ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, + AcceptHandler&& handler); + + /** Send an HTTP WebSocket Upgrade request and receive the response. This function is used to synchronously send the WebSocket upgrade HTTP request. The call blocks until one of the following conditions is true: - @li A HTTP request finishes sending and a HTTP response finishes - receiving. + @li The request is sent and the response is received. @li An error occurs on the stream @@ -706,18 +1847,18 @@ public: @param host The name of the remote host, required by the HTTP protocol. - @param resource The requesting URI, which may not be empty, + @param target The Request Target, which may not be empty, required by the HTTP protocol. @throws system_error Thrown on failure. @par Example @code - websocket::stream ws(io_service); + websocket::stream ws{io_service}; ... try { - ws.upgrade("localhost", "/"); + ws.handshake("localhost", "/"); } catch(...) { @@ -726,17 +1867,62 @@ public: @endcode */ void - handshake(boost::string_ref const& host, - boost::string_ref const& resource); + handshake(string_view host, string_view target); - /** Send a HTTP WebSocket Upgrade request and receive the response. + /** Send an HTTP WebSocket Upgrade request and receive the response. This function is used to synchronously send the WebSocket upgrade HTTP request. The call blocks until one of the following conditions is true: - @li A HTTP request finishes sending and a HTTP response finishes - receiving. + @li The request is sent and the response is received. + + @li An error occurs on the stream + + This function is implemented in terms of one or more calls to the + next layer's `read_some` and `write_some` functions. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param res The HTTP Upgrade response returned by the remote + endpoint. + + @param host The name of the remote host, + required by the HTTP protocol. + + @param target The Request Target, which may not be empty, + required by the HTTP protocol. + + @throws system_error Thrown on failure. + + @par Example + @code + websocket::stream ws{io_service}; + ... + try + { + response_type res; + ws.handshake(res, "localhost", "/"); + } + catch(...) + { + // An error occurred. + } + @endcode + */ + void + handshake(response_type& res, + string_view host, string_view target); + + /** Send an HTTP WebSocket Upgrade request and receive the response. + + This function is used to synchronously send the WebSocket + upgrade HTTP request. The call blocks until one of the + following conditions is true: + + @li The request is sent and the response is received. @li An error occurs on the stream @@ -750,17 +1936,136 @@ public: @param host The name of the remote host, required by the HTTP protocol. - @param resource The requesting URI, which may not be empty, + @param target The Request Target, which may not be empty, + required by the HTTP protocol. + + @param decorator A function object which will be called to modify + the HTTP request object generated by the implementation. This + could be used to set the User-Agent field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + request_type& req + ); @endcode + + @throws system_error Thrown on failure. + + @par Example + @code + websocket::stream ws{io_service}; + ... + try + { + ws.handshake("localhost", "/", + [](request_type& req) + { + req.set(field::user_agent, "Beast"); + }); + } + catch(...) + { + // An error occurred. + } + @endcode + */ + template + void + handshake_ex(string_view host, string_view target, + RequestDecorator const& decorator); + + /** Send an HTTP WebSocket Upgrade request and receive the response. + + This function is used to synchronously send the WebSocket + upgrade HTTP request. The call blocks until one of the + following conditions is true: + + @li The request is sent and the response is received. + + @li An error occurs on the stream + + This function is implemented in terms of one or more calls to the + next layer's `read_some` and `write_some` functions. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param res The HTTP Upgrade response returned by the remote + endpoint. + + @param host The name of the remote host, + required by the HTTP protocol. + + @param target The Request Target, which may not be empty, + required by the HTTP protocol. + + @param decorator A function object which will be called to modify + the HTTP request object generated by the implementation. This + could be used to set the User-Agent field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + request_type& req + ); @endcode + + @throws system_error Thrown on failure. + + @par Example + @code + websocket::stream ws{io_service}; + ... + try + { + response_type res; + ws.handshake(res, "localhost", "/", + [](request_type& req) + { + req.set(field::user_agent, "Beast"); + }); + } + catch(...) + { + // An error occurred. + } + @endcode + */ + template + void + handshake_ex(response_type& res, + string_view host, string_view target, + RequestDecorator const& decorator); + + /** Send an HTTP WebSocket Upgrade request and receive the response. + + This function is used to synchronously send the WebSocket + upgrade HTTP request. The call blocks until one of the + following conditions is true: + + @li The request is sent and the response is received. + + @li An error occurs on the stream + + This function is implemented in terms of one or more calls to the + next layer's `read_some` and `write_some` functions. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param host The name of the remote host, + required by the HTTP protocol. + + @param target The Request Target, which may not be empty, required by the HTTP protocol. @param ec Set to indicate what error occurred, if any. @par Example @code - websocket::stream ws(io_service); + websocket::stream ws{io_service}; ... error_code ec; - ws.upgrade(host, resource, ec); + ws.handshake(host, target, ec); if(ec) { // An error occurred. @@ -768,8 +2073,171 @@ public: @endcode */ void - handshake(boost::string_ref const& host, - boost::string_ref const& resource, error_code& ec); + handshake(string_view host, + string_view target, error_code& ec); + + /** Send an HTTP WebSocket Upgrade request and receive the response. + + This function is used to synchronously send the WebSocket + upgrade HTTP request. The call blocks until one of the + following conditions is true: + + @li The request is sent and the response is received. + + @li An error occurs on the stream + + This function is implemented in terms of one or more calls to the + next layer's `read_some` and `write_some` functions. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param host The name of the remote host, + required by the HTTP protocol. + + @param target The Request Target, which may not be empty, + required by the HTTP protocol. + + @param ec Set to indicate what error occurred, if any. + + @param res The HTTP Upgrade response returned by the remote + endpoint. If `ec is set, the return value is undefined. + + @par Example + @code + websocket::stream ws{io_service}; + ... + error_code ec; + response_type res; + ws.handshake(res, host, target, ec); + if(ec) + { + // An error occurred. + } + @endcode + */ + void + handshake(response_type& res, + string_view host, string_view target, error_code& ec); + + /** Send an HTTP WebSocket Upgrade request and receive the response. + + This function is used to synchronously send the WebSocket + upgrade HTTP request. The call blocks until one of the + following conditions is true: + + @li The request is sent and the response is received. + + @li An error occurs on the stream + + This function is implemented in terms of one or more calls to the + next layer's `read_some` and `write_some` functions. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param host The name of the remote host, + required by the HTTP protocol. + + @param target The Request Target, which may not be empty, + required by the HTTP protocol. + + @param decorator A function object which will be called to modify + the HTTP request object generated by the implementation. This + could be used to set the User-Agent field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + request_type& req + ); @endcode + + @param ec Set to indicate what error occurred, if any. + + @par Example + @code + websocket::stream ws{io_service}; + ... + error_code ec; + ws.handshake("localhost", "/", + [](request_type& req) + { + req.set(field::user_agent, "Beast"); + }, + ec); + if(ec) + { + // An error occurred. + } + @endcode + */ + template + void + handshake_ex(string_view host, + string_view target, RequestDecorator const& decorator, + error_code& ec); + + /** Send an HTTP WebSocket Upgrade request and receive the response. + + This function is used to synchronously send the WebSocket + upgrade HTTP request. The call blocks until one of the + following conditions is true: + + @li The request is sent and the response is received. + + @li An error occurs on the stream + + This function is implemented in terms of one or more calls to the + next layer's `read_some` and `write_some` functions. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param res The HTTP Upgrade response returned by the remote + endpoint. + + @param host The name of the remote host, + required by the HTTP protocol. + + @param target The Request Target, which may not be empty, + required by the HTTP protocol. + + @param decorator A function object which will be called to modify + the HTTP request object generated by the implementation. This + could be used to set the User-Agent field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + request_type& req + ); @endcode + + @param ec Set to indicate what error occurred, if any. + + @par Example + @code + websocket::stream ws{io_service}; + ... + error_code ec; + response_type res; + ws.handshake(res, "localhost", "/", + [](request_type& req) + { + req.set(field::user_agent, "Beast"); + }, + ec); + if(ec) + { + // An error occurred. + } + @endcode + */ + template + void + handshake_ex(response_type& res, + string_view host, string_view target, + RequestDecorator const& decorator, error_code& ec); /** Start an asynchronous operation to send an upgrade request and receive the response. @@ -779,10 +2247,9 @@ public: operation will continue until one of the following conditions is true: - @li A HTTP request finishes sending and a HTTP response finishes - receiving. + @li The request is sent and the response is received. - @li An error occurs on the stream. + @li An error occurs on the stream This operation is implemented in terms of one or more calls to the next layer's `async_read_some` and `async_write_some` functions, and @@ -797,15 +2264,15 @@ public: @param host The name of the remote host, required by the HTTP protocol. Copies may be made as needed. - @param resource The requesting URI, which may not be empty, - required by the HTTP protocol. Copies may be made as - needed. + @param target The Request Target, which may not be empty, + required by the HTTP protocol. Copies of this parameter may + be made as needed. - @param h The handler to be called when the request completes. + @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 // result of operation + error_code const& ec // Result of operation ); @endcode Regardless of whether the asynchronous operation completes immediately or not, the handler will not be invoked from within @@ -813,14 +2280,194 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion< - HandshakeHandler, void(error_code)>::result_type + async_return_type< + HandshakeHandler, void(error_code)> #endif - async_handshake(boost::string_ref const& host, - boost::string_ref const& resource, HandshakeHandler&& h); + async_handshake(string_view host, + string_view target, HandshakeHandler&& handler); + + /** Start an asynchronous operation to send an upgrade request and receive the response. + + This function is used to asynchronously send the HTTP WebSocket + upgrade request and receive the HTTP WebSocket Upgrade response. + This function call always returns immediately. The asynchronous + operation will continue until one of the following conditions is + true: + + @li The request is sent and the response is received. + + @li An error occurs on the stream + + This operation is implemented in terms of one or more calls to the + next layer's `async_read_some` and `async_write_some` functions, and + is known as a composed operation. The program must ensure + that the stream performs no other operations until this operation + completes. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param res The HTTP Upgrade response returned by the remote + endpoint. The caller must ensure this object is valid for at + least until the completion handler is invoked. + + @param host The name of the remote host, required by + the HTTP protocol. Copies may be made as needed. + + @param target The Request Target, which may not be empty, + required by the HTTP protocol. Copies of this parameter may + be made as needed. + + @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& 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 +#if BEAST_DOXYGEN + void_or_deduced +#else + async_return_type< + HandshakeHandler, void(error_code)> +#endif + async_handshake(response_type& res, + string_view host, string_view target, + HandshakeHandler&& handler); + + /** Start an asynchronous operation to send an upgrade request and receive the response. + + This function is used to asynchronously send the HTTP WebSocket + upgrade request and receive the HTTP WebSocket Upgrade response. + This function call always returns immediately. The asynchronous + operation will continue until one of the following conditions is + true: + + @li The request is sent and the response is received. + + @li An error occurs on the stream + + This operation is implemented in terms of one or more calls to the + next layer's `async_read_some` and `async_write_some` functions, and + is known as a composed operation. The program must ensure + that the stream performs no other operations until this operation + completes. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param host The name of the remote host, required by + the HTTP protocol. Copies may be made as needed. + + @param target The Request Target, which may not be empty, + required by the HTTP protocol. Copies of this parameter may + be made as needed. + + @param decorator A function object which will be called to modify + the HTTP request object generated by the implementation. This + could be used to set the User-Agent field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + request_type& req + ); @endcode + + @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& 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 +#if BEAST_DOXYGEN + void_or_deduced +#else + async_return_type< + HandshakeHandler, void(error_code)> +#endif + async_handshake_ex(string_view host, + string_view target, RequestDecorator const& decorator, + HandshakeHandler&& handler); + + /** Start an asynchronous operation to send an upgrade request and receive the response. + + This function is used to asynchronously send the HTTP WebSocket + upgrade request and receive the HTTP WebSocket Upgrade response. + This function call always returns immediately. The asynchronous + operation will continue until one of the following conditions is + true: + + @li The request is sent and the response is received. + + @li An error occurs on the stream + + This operation is implemented in terms of one or more calls to the + next layer's `async_read_some` and `async_write_some` functions, and + is known as a composed operation. The program must ensure + that the stream performs no other operations until this operation + completes. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param res The HTTP Upgrade response returned by the remote + endpoint. The caller must ensure this object is valid for at + least until the completion handler is invoked. + + @param host The name of the remote host, required by + the HTTP protocol. Copies may be made as needed. + + @param target The Request Target, which may not be empty, + required by the HTTP protocol. Copies of this parameter may + be made as needed. + + @param decorator A function object which will be called to modify + the HTTP request object generated by the implementation. This + could be used to set the User-Agent field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + request_type& req + ); @endcode + + @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& 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 +#if BEAST_DOXYGEN + void_or_deduced +#else + async_return_type< + HandshakeHandler, void(error_code)> +#endif + async_handshake_ex(response_type& res, + string_view host, string_view target, + RequestDecorator const& decorator, + HandshakeHandler&& handler); /** Send a WebSocket close frame. @@ -915,7 +2562,7 @@ public: function signature of the handler must be: @code void handler( - error_code const& error // Result of operation + error_code const& ec // Result of operation ); @endcode Regardless of whether the asynchronous operation completes @@ -924,11 +2571,11 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion< - CloseHandler, void(error_code)>::result_type + async_return_type< + CloseHandler, void(error_code)> #endif async_close(close_reason const& cr, CloseHandler&& handler); @@ -997,7 +2644,7 @@ public: function signature of the handler must be: @code void handler( - error_code const& error // Result of operation + error_code const& ec // Result of operation ); @endcode Regardless of whether the asynchronous operation completes @@ -1006,11 +2653,11 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion< - WriteHandler, void(error_code)>::result_type + async_return_type< + WriteHandler, void(error_code)> #endif async_ping(ping_data const& payload, WriteHandler&& handler); @@ -1094,7 +2741,7 @@ public: function signature of the handler must be: @code void handler( - error_code const& error // Result of operation + error_code const& ec // Result of operation ); @endcode Regardless of whether the asynchronous operation completes @@ -1103,11 +2750,11 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion< - WriteHandler, void(error_code)>::result_type + async_return_type< + WriteHandler, void(error_code)> #endif async_pong(ping_data const& payload, WriteHandler&& handler); @@ -1123,33 +2770,32 @@ public: This call is implemented in terms of one or more calls to the stream's `read_some` and `write_some` operations. - Upon a success, op is set to either binary or text depending on - the message type, and the input area of the stream buffer will - hold all the message payload bytes (which may be zero in length). + Upon a success, the input area of the stream buffer will + hold the received message payload bytes (which may be zero + in length). The functions @ref got_binary and @ref got_text + may be used to query the stream and determine the type + of the last received message. During reads, the implementation handles control frames as follows: @li A pong frame is sent when a ping frame is received. - @li The @ref ping_callback is invoked when a ping frame + @li The @ref control_callback is invoked when a ping frame or pong frame is received. @li The WebSocket close procedure is started if a close frame is received. In this case, the operation will eventually complete with the error set to @ref error::closed. - @param op A value to receive the message type. - This object must remain valid until the handler is called. - - @param dynabuf A dynamic buffer to hold the message data after + @param buffer A dynamic buffer to hold the message data after any masking or decompression has been applied. @throws system_error Thrown on failure. */ template void - read(opcode& op, DynamicBuffer& dynabuf); + read(DynamicBuffer& buffer); /** Read a message from the stream. @@ -1163,14 +2809,16 @@ public: This call is implemented in terms of one or more calls to the stream's `read_some` and `write_some` operations. - Upon a success, op is set to either binary or text depending on - the message type, and the input area of the stream buffer will - hold all the message payload bytes (which may be zero in length). + Upon a success, the input area of the stream buffer will + hold the received message payload bytes (which may be zero + in length). The functions @ref got_binary and @ref got_text + may be used to query the stream and determine the type + of the last received message. During reads, the implementation handles control frames as follows: - @li The @ref ping_callback is invoked when a ping frame + @li The @ref control_callback is invoked when a ping frame or pong frame is received. @li A pong frame is sent when a ping frame is received. @@ -1179,17 +2827,14 @@ public: is received. In this case, the operation will eventually complete with the error set to @ref error::closed. - @param op A value to receive the message type. - This object must remain valid until the handler is called. - - @param dynabuf A dynamic buffer to hold the message data after + @param buffer A dynamic buffer to hold the message data after any masking or decompression has been applied. @param ec Set to indicate what error occurred, if any. */ template void - read(opcode& op, DynamicBuffer& dynabuf, error_code& ec); + read(DynamicBuffer& buffer, error_code& ec); /** Start an asynchronous operation to read a message from the stream. @@ -1208,14 +2853,16 @@ public: ensure that the stream performs no other reads until this operation completes. - Upon a success, op is set to either binary or text depending on - the message type, and the input area of the stream buffer will - hold all the message payload bytes (which may be zero in length). + Upon a success, the input area of the stream buffer will + hold the received message payload bytes (which may be zero + in length). The functions @ref got_binary and @ref got_text + may be used to query the stream and determine the type + of the last received message. During reads, the implementation handles control frames as follows: - @li The @ref ping_callback is invoked when a ping frame + @li The @ref control_callback is invoked when a ping frame or pong frame is received. @li A pong frame is sent when a ping frame is received. @@ -1230,10 +2877,7 @@ public: read and asynchronous write operation pending simultaneously (a user initiated call to @ref async_close counts as a write). - @param op A value to receive the message type. - This object must remain valid until the handler is called. - - @param dynabuf A dynamic buffer to hold the message data after + @param buffer A dynamic buffer to hold the message data after any masking or decompression has been applied. This object must remain valid until the handler is called. @@ -1242,7 +2886,7 @@ public: function signature of the handler must be: @code void handler( - error_code const& error // Result of operation + error_code const& ec // Result of operation ); @endcode Regardless of whether the asynchronous operation completes @@ -1251,13 +2895,13 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion< - ReadHandler, void(error_code)>::result_type + async_return_type< + ReadHandler, void(error_code)> #endif - async_read(opcode& op, DynamicBuffer& dynabuf, ReadHandler&& handler); + async_read(DynamicBuffer& buffer, ReadHandler&& handler); /** Read a message frame from the stream. @@ -1272,17 +2916,10 @@ public: This call is implemented in terms of one or more calls to the stream's `read_some` and `write_some` operations. - Upon success, `fi` is filled out to reflect the message payload - contents. `op` is set to binary or text, and the `fin` flag - indicates if all the message data has been read in. To read the - entire message, callers should keep calling @ref read_frame - until `fi.fin == true`. A message with no payload will have - `fi.fin == true`, and zero bytes placed into the stream buffer. - During reads, the implementation handles control frames as follows: - @li The @ref ping_callback is invoked when a ping frame + @li The @ref control_callback is invoked when a ping frame or pong frame is received. @li A pong frame is sent when a ping frame is received. @@ -1291,16 +2928,16 @@ public: is received. In this case, the operation will eventually complete with the error set to @ref error::closed. - @param fi An object to store metadata about the message. - - @param dynabuf A dynamic buffer to hold the message data after + @param buffer A dynamic buffer to hold the message data after any masking or decompression has been applied. + @return `true` if this is the last frame of the message. + @throws system_error Thrown on failure. */ template - void - read_frame(frame_info& fi, DynamicBuffer& dynabuf); + bool + read_frame(DynamicBuffer& buffer); /** Read a message frame from the stream. @@ -1315,17 +2952,10 @@ public: This call is implemented in terms of one or more calls to the stream's `read_some` and `write_some` operations. - Upon success, `fi` is filled out to reflect the message payload - contents. `op` is set to binary or text, and the `fin` flag - indicates if all the message data has been read in. To read the - entire message, callers should keep calling @ref read_frame - until `fi.fin == true`. A message with no payload will have - `fi.fin == true`, and zero bytes placed into the stream buffer. - During reads, the implementation handles control frames as follows: - @li The @ref ping_callback is invoked when a ping frame + @li The @ref control_callback is invoked when a ping frame or pong frame is received. @li A pong frame is sent when a ping frame is received. @@ -1334,16 +2964,16 @@ public: is received. In this case, the operation will eventually complete with the error set to @ref error::closed. - @param fi An object to store metadata about the message. - - @param dynabuf A dynamic buffer to hold the message data after + @param buffer A dynamic buffer to hold the message data after any masking or decompression has been applied. @param ec Set to indicate what error occurred, if any. + + @return `true` if this is the last frame of the message. */ template - void - read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec); + bool + read_frame(DynamicBuffer& buffer, error_code& ec); /** Start an asynchronous operation to read a message frame from the stream. @@ -1362,18 +2992,10 @@ public: ensure that the stream performs no other reads until this operation completes. - Upon a successful completion, `fi` is filled out to reflect the - message payload contents. `op` is set to binary or text, and the - `fin` flag indicates if all the message data has been read in. - To read the entire message, callers should keep calling - @ref read_frame until `fi.fin == true`. A message with no payload - will have `fi.fin == true`, and zero bytes placed into the stream - buffer. - During reads, the implementation handles control frames as follows: - @li The @ref ping_callback is invoked when a ping frame + @li The @ref control_callback is invoked when a ping frame or pong frame is received. @li A pong frame is sent when a ping frame is received. @@ -1388,10 +3010,7 @@ public: read and asynchronous write operation pending simultaneously (a user initiated call to @ref async_close counts as a write). - @param fi An object to store metadata about the message. - This object must remain valid until the handler is called. - - @param dynabuf A dynamic buffer to hold the message data after + @param buffer A dynamic buffer to hold the message data after any masking or decompression has been applied. This object must remain valid until the handler is called. @@ -1400,7 +3019,8 @@ public: function signature of the handler must be: @code void handler( - error_code const& error // Result of operation + error_code const& ec, // Result of operation + bool fin // `true` if this is the last frame ); @endcode Regardless of whether the asynchronous operation completes @@ -1409,14 +3029,12 @@ public: manner equivalent to using boost::asio::io_service::post(). */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion< - ReadHandler, void(error_code)>::result_type + async_return_type #endif - async_read_frame(frame_info& fi, - DynamicBuffer& dynabuf, ReadHandler&& handler); + async_read_frame(DynamicBuffer& buffer, ReadHandler&& handler); /** Write a message to the stream. @@ -1431,7 +3049,7 @@ public: This operation is implemented in terms of one or more calls to the next layer's `write_some` function. - The current setting of the @ref message_type option controls + The current setting of the @ref binary option controls whether the message opcode is set to text or binary. If the @ref auto_fragment option is set, the message will be split into one or more frames as necessary. The actual payload contents @@ -1466,7 +3084,7 @@ public: This operation is implemented in terms of one or more calls to the next layer's `write_some` function. - The current setting of the @ref message_type option controls + The current setting of the @ref binary option controls whether the message opcode is set to text or binary. If the @ref auto_fragment option is set, the message will be split into one or more frames as necessary. The actual payload contents @@ -1508,7 +3126,7 @@ public: stream::async_write, stream::async_write_frame, or stream::async_close). - The current setting of the @ref message_type option controls + The current setting of the @ref binary option controls whether the message opcode is set to text or binary. If the @ref auto_fragment option is set, the message will be split into one or more frames as necessary. The actual payload contents @@ -1526,7 +3144,7 @@ public: function signature of the handler must be: @code void handler( - error_code const& error // Result of operation + error_code const& ec // Result of operation ); @endcode Regardless of whether the asynchronous operation completes @@ -1535,11 +3153,11 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion< - WriteHandler, void(error_code)>::result_type + async_return_type< + WriteHandler, void(error_code)> #endif async_write(ConstBufferSequence const& buffers, WriteHandler&& handler); @@ -1561,8 +3179,8 @@ public: If this is the beginning of a new message, the message opcode will be set to text or binary as per the current setting of - the @ref message_type option. The actual payload sent - may be transformed as per the WebSocket protocol settings. + the @ref binary option. The actual payload sent may be + transformed as per the WebSocket protocol settings. @param fin `true` if this is the last frame in the message. @@ -1593,8 +3211,8 @@ public: If this is the beginning of a new message, the message opcode will be set to text or binary as per the current setting of - the @ref message_type option. The actual payload sent - may be transformed as per the WebSocket protocol settings. + the @ref binary option. The actual payload sent may be + transformed as per the WebSocket protocol settings. @param fin `true` if this is the last frame in the message. @@ -1630,8 +3248,8 @@ public: If this is the beginning of a new message, the message opcode will be set to text or binary as per the current setting of - the @ref message_type option. The actual payload sent - may be transformed as per the WebSocket protocol settings. + the @ref binary option. The actual payload sent may be + transformed as per the WebSocket protocol settings. @param fin A bool indicating whether or not the frame is the last frame in the corresponding WebSockets message. @@ -1647,21 +3265,21 @@ public: 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 + error_code const& ec // Result of operation ); @endcode */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else - typename async_completion< - WriteHandler, void(error_code)>::result_type + async_return_type< + WriteHandler, void(error_code)> #endif async_write_frame(bool fin, ConstBufferSequence const& buffers, WriteHandler&& handler); private: - template class accept_op; + template class accept_op; template class close_op; template class handshake_op; template class ping_op; @@ -1671,22 +3289,56 @@ private: template class read_op; template class read_frame_op; + static + void + default_decorate_req(request_type&) + { + } + + static + void + default_decorate_res(response_type&) + { + } + void reset(); - http::request - build_request(boost::string_ref const& host, - boost::string_ref const& resource, - std::string& key); - - template - http::response - build_response(http::request const& req); - - template + template void - do_response(http::response const& resp, - boost::string_ref const& key, error_code& ec); + do_accept(Decorator const& decorator, + error_code& ec); + + template + void + do_accept(http::header> const& req, + Decorator const& decorator, error_code& ec); + + template + void + do_handshake(response_type* res_p, + string_view host, + string_view target, + RequestDecorator const& decorator, + error_code& ec); + + template + request_type + build_request(detail::sec_ws_key_type& key, + string_view host, + string_view target, + Decorator const& decorator); + + template + response_type + build_response(http::header> const& req, + Decorator const& decorator); + + void + do_response(http::header const& resp, + detail::sec_ws_key_type const& key, error_code& ec); }; } // websocket diff --git a/include/beast/zlib.hpp b/include/beast/zlib.hpp index 16bbb78fcc..8fb12d5705 100644 --- a/include/beast/zlib.hpp +++ b/include/beast/zlib.hpp @@ -11,6 +11,8 @@ #include #include +#include #include +#include #endif diff --git a/include/beast/zlib/deflate_stream.hpp b/include/beast/zlib/deflate_stream.hpp index aae355d06a..b94e9e3539 100644 --- a/include/beast/zlib/deflate_stream.hpp +++ b/include/beast/zlib/deflate_stream.hpp @@ -4,6 +4,23 @@ // Distributed under the 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_ZLIB_DEFLATE_STREAM_HPP +#define BEAST_ZLIB_DEFLATE_STREAM_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace zlib { + // This is a derivative work based on Zlib, copyright below: /* Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler @@ -32,22 +49,6 @@ (zlib format), rfc1951 (deflate format) and rfc1952 (gzip format). */ -#ifndef BEAST_ZLIB_DEFLATE_STREAM_HPP -#define BEAST_ZLIB_DEFLATE_STREAM_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace zlib { - /** Raw deflate compressor. This is a port of zlib's "deflate" functionality to C++. @@ -136,7 +137,7 @@ public: of bytes needed to store the result of compressing a block of data based on the current compression level and strategy. - @param bytes The size of the uncompressed data. + @param sourceLen The size of the uncompressed data. @return The maximum number of resulting compressed bytes. */ diff --git a/include/beast/zlib/detail/deflate_stream.hpp b/include/beast/zlib/detail/deflate_stream.hpp index 50358076cd..b51baff1b0 100644 --- a/include/beast/zlib/detail/deflate_stream.hpp +++ b/include/beast/zlib/detail/deflate_stream.hpp @@ -39,7 +39,10 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -132,7 +135,7 @@ protected: static std::uint16_t constexpr maxMatch = 258; // Can't change minMatch without also changing code, see original zlib - static_assert(minMatch==3, ""); + BOOST_STATIC_ASSERT(minMatch == 3); // end of block literal code static std::uint16_t constexpr END_BLOCK = 256; @@ -645,9 +648,10 @@ protected: init(); } + template static - unsigned - bi_reverse(unsigned code, int len); + Unsigned + bi_reverse(Unsigned code, unsigned len); template static @@ -665,7 +669,7 @@ protected: template std::size_t doUpperBound (std::size_t sourceLen) const; template void doTune (int good_length, int max_lazy, int nice_length, int max_chain); template void doParams (z_params& zs, int level, Strategy strategy, error_code& ec); - template void doWrite (z_params& zs, Flush flush, error_code& ec); + template void doWrite (z_params& zs, boost::optional flush, error_code& ec); template void doDictionary (Byte const* dict, uInt dictLength, error_code& ec); template void doPrime (int bits, int value, error_code& ec); template void doPending (unsigned* value, int* bits); @@ -741,12 +745,15 @@ protected: //-------------------------------------------------------------------------- // Reverse the first len bits of a code +template inline -unsigned +Unsigned deflate_stream:: -bi_reverse(unsigned code, int len) +bi_reverse(Unsigned code, unsigned len) { - unsigned res = 0; + BOOST_STATIC_ASSERT(std::is_unsigned::value); + BOOST_ASSERT(len <= 8 * sizeof(unsigned)); + Unsigned res = 0; do { res |= code & 1; @@ -807,7 +814,7 @@ deflate_stream::get_lut() -> //std::uint16_t bl_count[maxBits+1]; // Initialize the mapping length (0..255) -> length code (0..28) - int length = 0; + std::uint8_t length = 0; for(std::uint8_t code = 0; code < lengthCodes-1; ++code) { tables.base_length[code] = length; @@ -815,7 +822,7 @@ deflate_stream::get_lut() -> for(unsigned n = 0; n < run; ++n) tables.length_code[length++] = code; } - BOOST_ASSERT(length == 256); + BOOST_ASSERT(length == 0); // Note that the length 255 (match length 258) can be represented // in two different ways: code 284 + 5 bits or code 285, so we // overwrite length_code[255] to use the best encoding: @@ -894,19 +901,17 @@ doReset( if(windowBits == 8) windowBits = 9; - using beast::detail::make_exception; - if(level < 0 || level > 9) - throw make_exception( - "invalid level", __FILE__, __LINE__); + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid level"}); if(windowBits < 8 || windowBits > 15) - throw make_exception( - "invalid windowBits", __FILE__, __LINE__); + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid windowBits"}); if(memLevel < 1 || memLevel > MAX_MEM_LEVEL) - throw make_exception( - "invalid memLevel", __FILE__, __LINE__); + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid memLevel"}); w_bits_ = windowBits; @@ -998,7 +1003,7 @@ doParams(z_params& zs, int level, Strategy strategy, error_code& ec) // Flush the last buffer: doWrite(zs, Flush::block, ec); if(ec == error::need_buffers && pending_ == 0) - ec = {}; + ec.assign(0, ec.category()); } if(level_ != level) { @@ -1011,10 +1016,14 @@ doParams(z_params& zs, int level, Strategy strategy, error_code& ec) strategy_ = strategy; } +// VFALCO boost::optional param is a workaround for +// gcc "maybe uninitialized" warning +// https://github.com/vinniefalco/Beast/issues/532 +// template void deflate_stream:: -doWrite(z_params& zs, Flush flush, error_code& ec) +doWrite(z_params& zs, boost::optional flush, error_code& ec) { maybe_init(); @@ -1050,8 +1059,9 @@ doWrite(z_params& zs, Flush flush, error_code& ec) return; } } - else if(zs.avail_in == 0 && flush <= old_flush && - flush != Flush::finish) + else if(zs.avail_in == 0 && ( + old_flush && flush <= *old_flush + ) && flush != Flush::finish) { /* Make sure there is something to do and avoid duplicate consecutive * flushes. For repeated and useless calls with Flush::finish, we keep @@ -1078,14 +1088,14 @@ doWrite(z_params& zs, Flush flush, error_code& ec) switch(strategy_) { case Strategy::huffman: - bstate = deflate_huff(zs, flush); + bstate = deflate_huff(zs, flush.get()); break; case Strategy::rle: - bstate = deflate_rle(zs, flush); + bstate = deflate_rle(zs, flush.get()); break; default: { - bstate = (this->*(get_config(level_).func))(zs, flush); + bstate = (this->*(get_config(level_).func))(zs, flush.get()); break; } } @@ -1273,7 +1283,8 @@ init() if(! buf_ || buf_size_ != needed) { - buf_.reset(new std::uint8_t[needed]); + buf_ = boost::make_unique_noinit< + std::uint8_t[]>(needed); buf_size_ = needed; } @@ -1436,7 +1447,7 @@ gen_bitlen(tree_desc *desc) std::uint16_t f; // frequency int overflow = 0; // number of elements with bit length too large - std::fill(&bl_count_[0], &bl_count_[maxBits+1], 0); + std::fill(&bl_count_[0], &bl_count_[maxBits+1], std::uint16_t{0}); /* In a first pass, compute the optimal bit lengths (which may * overflow in the case of the bit length tree). @@ -1615,7 +1626,7 @@ scan_tree( int prevlen = -1; // last emitted length int curlen; // length of current code int nextlen = tree[0].dl; // length of next code - int count = 0; // repeat count of the current code + std::uint16_t count = 0; // repeat count of the current code int max_count = 7; // max repeat count int min_count = 4; // min repeat count @@ -2283,18 +2294,18 @@ fill_window(z_params& zs) if(high_water_ < window_size_) { std::uint32_t curr = strstart_ + (std::uint32_t)(lookahead_); - std::uint32_t init; + std::uint32_t winit; if(high_water_ < curr) { /* Previous high water mark below current data -- zero kWinInit bytes or up to end of window, whichever is less. */ - init = window_size_ - curr; - if(init > kWinInit) - init = kWinInit; - std::memset(window_ + curr, 0, (unsigned)init); - high_water_ = curr + init; + winit = window_size_ - curr; + if(winit > kWinInit) + winit = kWinInit; + std::memset(window_ + curr, 0, (unsigned)winit); + high_water_ = curr + winit; } else if(high_water_ < (std::uint32_t)curr + kWinInit) { @@ -2302,11 +2313,11 @@ fill_window(z_params& zs) plus kWinInit -- zero out to current data plus kWinInit, or up to end of window, whichever is less. */ - init = (std::uint32_t)curr + kWinInit - high_water_; - if(init > window_size_ - high_water_) - init = window_size_ - high_water_; - std::memset(window_ + high_water_, 0, (unsigned)init); - high_water_ += init; + winit = (std::uint32_t)curr + kWinInit - high_water_; + if(winit > window_size_ - high_water_) + winit = window_size_ - high_water_; + std::memset(window_ + high_water_, 0, (unsigned)winit); + high_water_ += winit; } } } @@ -2626,8 +2637,8 @@ f_fast(z_params& zs, Flush flush) -> } if(match_length_ >= minMatch) { - tr_tally_dist(strstart_ - match_start_, - match_length_ - minMatch, bflush); + tr_tally_dist(static_cast(strstart_ - match_start_), + static_cast(match_length_ - minMatch), bflush); lookahead_ -= match_length_; @@ -2762,8 +2773,9 @@ f_slow(z_params& zs, Flush flush) -> /* Do not insert strings in hash table beyond this. */ uInt max_insert = strstart_ + lookahead_ - minMatch; - tr_tally_dist(strstart_ -1 - prev_match_, - prev_length_ - minMatch, bflush); + tr_tally_dist( + static_cast(strstart_ -1 - prev_match_), + static_cast(prev_length_ - minMatch), bflush); /* Insert in hash table all strings up to the end of the match. * strstart-1 and strstart are already inserted. If there is not @@ -2887,7 +2899,9 @@ f_rle(z_params& zs, Flush flush) -> /* Emit match if have run of minMatch or longer, else emit literal */ if(match_length_ >= minMatch) { - tr_tally_dist(1, match_length_ - minMatch, bflush); + tr_tally_dist(std::uint16_t{1}, + static_cast(match_length_ - minMatch), + bflush); lookahead_ -= match_length_; strstart_ += match_length_; diff --git a/include/beast/zlib/detail/inflate_stream.hpp b/include/beast/zlib/detail/inflate_stream.hpp index fb2d4ce074..3337278679 100644 --- a/include/beast/zlib/detail/inflate_stream.hpp +++ b/include/beast/zlib/detail/inflate_stream.hpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -233,8 +234,8 @@ inflate_stream:: doReset(int windowBits) { if(windowBits < 8 || windowBits > 15) - throw beast::detail::make_exception( - "windowBits out of range", __FILE__, __LINE__); + BOOST_THROW_EXCEPTION(std::domain_error{ + "windowBits out of range"}); w_.reset(windowBits); bi_.flush(); @@ -708,8 +709,8 @@ doWrite(z_params& zs, Flush flush, error_code& ec) case SYNC: default: - throw beast::detail::make_exception( - "stream error", __FILE__, __LINE__); + BOOST_THROW_EXCEPTION(std::logic_error{ + "stream error"}); } } } @@ -935,8 +936,8 @@ inflate_table( auto const not_enough = [] { - throw beast::detail::make_exception( - "insufficient output size when inflating tables", __FILE__, __LINE__); + BOOST_THROW_EXCEPTION(std::logic_error{ + "insufficient output size when inflating tables"}); }; // check available table space @@ -1066,10 +1067,10 @@ get_fixed_tables() -> std::uint16_t lens[320]; std::uint16_t work[288]; - std::fill(&lens[ 0], &lens[144], 8); - std::fill(&lens[144], &lens[256], 9); - std::fill(&lens[256], &lens[280], 7); - std::fill(&lens[280], &lens[288], 8); + std::fill(&lens[ 0], &lens[144], std::uint16_t{8}); + std::fill(&lens[144], &lens[256], std::uint16_t{9}); + std::fill(&lens[256], &lens[280], std::uint16_t{7}); + std::fill(&lens[280], &lens[288], std::uint16_t{8}); { error_code ec; @@ -1077,7 +1078,7 @@ get_fixed_tables() -> inflate_table(build::lens, lens, 288, &next, &lenbits, work, ec); if(ec) - throw std::logic_error{ec.message()}; + BOOST_THROW_EXCEPTION(std::logic_error{ec.message()}); } // VFALCO These fixups are from ZLib @@ -1089,11 +1090,11 @@ get_fixed_tables() -> { error_code ec; auto next = &dist_[0]; - std::fill(&lens[0], &lens[32], 5); + std::fill(&lens[0], &lens[32], std::uint16_t{5}); inflate_table(build::dists, lens, 32, &next, &distbits, work, ec); if(ec) - throw std::logic_error{ec.message()}; + BOOST_THROW_EXCEPTION(std::logic_error{ec.message()}); } } }; diff --git a/include/beast/zlib/detail/window.hpp b/include/beast/zlib/detail/window.hpp index 2a277710b8..a671b2342f 100644 --- a/include/beast/zlib/detail/window.hpp +++ b/include/beast/zlib/detail/window.hpp @@ -36,6 +36,7 @@ #define BEAST_ZLIB_DETAIL_WINDOW_HPP #include +#include #include #include #include @@ -126,7 +127,8 @@ window:: write(std::uint8_t const* in, std::size_t n) { if(! p_) - p_.reset(new std::uint8_t[capacity_]); + p_ = boost::make_unique< + std::uint8_t[]>(capacity_); if(n >= capacity_) { i_ = 0; diff --git a/include/beast/zlib/error.hpp b/include/beast/zlib/error.hpp index d593917baa..7ab179536d 100644 --- a/include/beast/zlib/error.hpp +++ b/include/beast/zlib/error.hpp @@ -4,6 +4,16 @@ // Distributed under the 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_ZLIB_ERROR_HPP +#define BEAST_ZLIB_ERROR_HPP + +#include +#include + +namespace beast { +namespace zlib { + // This is a derivative work based on Zlib, copyright below: /* Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler @@ -32,15 +42,6 @@ (zlib format), rfc1951 (deflate format) and rfc1952 (gzip format). */ -#ifndef BEAST_ZLIB_ERROR_HPP -#define BEAST_ZLIB_ERROR_HPP - -#include -#include - -namespace beast { -namespace zlib { - /** Error codes returned by the codec. */ enum class error diff --git a/include/beast/zlib/impl/error.ipp b/include/beast/zlib/impl/error.ipp index f5cf12082d..50b28423d2 100644 --- a/include/beast/zlib/impl/error.ipp +++ b/include/beast/zlib/impl/error.ipp @@ -58,7 +58,7 @@ public: const char* name() const noexcept override { - return "zlib"; + return "beast.zlib"; } std::string @@ -85,7 +85,7 @@ public: case error::general: default: - return "zlib error"; + return "beast.zlib error"; } } diff --git a/include/beast/zlib/inflate_stream.hpp b/include/beast/zlib/inflate_stream.hpp index e6c42021e6..dd4df6bb7e 100644 --- a/include/beast/zlib/inflate_stream.hpp +++ b/include/beast/zlib/inflate_stream.hpp @@ -4,6 +4,13 @@ // Distributed under the 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_ZLIB_INFLATE_STREAM_HPP +#define BEAST_ZLIB_INFLATE_STREAM_HPP + +#include +#include + // This is a derivative work based on Zlib, copyright below: /* Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler @@ -32,12 +39,6 @@ (zlib format), rfc1951 (deflate format) and rfc1952 (gzip format). */ -#ifndef BEAST_ZLIB_INFLATE_STREAM_HPP -#define BEAST_ZLIB_INFLATE_STREAM_HPP - -#include -#include - namespace beast { namespace zlib { @@ -181,7 +182,7 @@ public: `Flush::trees` is used, and when `write` avoids the allocation of memory for a sliding window when `Flush::finsih` is used. - If a preset dictionary is needed after this call (see @ref dictionary below), + If a preset dictionary is needed after this call, `write` sets `zs.adler` to the Adler-32 checksum of the dictionary chosen by the compressor and returns `error::need_dictionary`; otherwise it sets `zs.adler` to the Adler-32 checksum of all output produced so far (that is, diff --git a/include/beast/zlib/zlib.hpp b/include/beast/zlib/zlib.hpp index b06f679e19..217bb191ce 100644 --- a/include/beast/zlib/zlib.hpp +++ b/include/beast/zlib/zlib.hpp @@ -4,6 +4,14 @@ // Distributed under the 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_ZLIB_ZLIB_HPP +#define BEAST_ZLIB_ZLIB_HPP + +#include +#include +#include + // This is a derivative work based on Zlib, copyright below: /* Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler @@ -32,13 +40,6 @@ (zlib format), rfc1951 (deflate format) and rfc1952 (gzip format). */ -#ifndef BEAST_ZLIB_ZLIB_HPP -#define BEAST_ZLIB_ZLIB_HPP - -#include -#include -#include - namespace beast { namespace zlib { diff --git a/scripts/blacklist.supp b/scripts/blacklist.supp index 08968f04de..09bc1abed2 100644 --- a/scripts/blacklist.supp +++ b/scripts/blacklist.supp @@ -14,26 +14,16 @@ # Try if at all possible to filter only functions using fun:regex # Remember you must use mangled symbol names with fun:regex - -#### Compile time filters for ubsan #### +# boost/lexical_cast.hpp:1625:43: runtime error: downcast of address 0x7fbb4fffbce8 which does not point to an object of type 'buffer_t' (aka 'parser_buf >, char>') +# Fixed in Boost 1.63.0 https://svn.boost.org/trac/boost/ticket/12889 +# +fun:*shl_input_streamable* ## The well known ubsan failure in libstdc++ extant for years :) # Line 96:24: runtime error: load of value 4294967221, which is not a valid value for type 'std::_Ios_Fmtflags' -fun:*_Ios_Fmtflags* +# +#fun:*_Ios_Fmtflags* # boost/any.hpp:259:16: runtime error: downcast of address 0x000004392e70 which does not point to an object of type 'any::holder' -fun:*any_cast* - -# boost/lexical_cast.hpp:1625:43: runtime error: downcast of address 0x7fbb4fffbce8 which does not point to an object of type 'buffer_t' (aka 'parser_buf >, char>') -fun:*shl_input_streamable* - - - - -#### Compile time filters for asan #### - - -#### Compile time filters for msan #### - - -#### Compile time filters for tsan #### +# +#fun:*any_cast* diff --git a/scripts/build-and-test.sh b/scripts/build-and-test.sh index e62e93aa8f..c09f13d037 100755 --- a/scripts/build-and-test.sh +++ b/scripts/build-and-test.sh @@ -9,6 +9,8 @@ shopt -s globstar ################################## ENVIRONMENT ################################# +DO_VALGRIND=${DO_VALGRIND:-false} + # If not CI, then set some defaults if [[ "${CI:-}" == "" ]]; then TRAVIS_BRANCH=${TRAVIS_BRANCH:-feature} @@ -43,6 +45,9 @@ elif [[ $(uname -s) == "Linux" ]]; then if [[ "${TRAVIS}" == "true" && ${NUM_PROCESSORS:=2} > ${num_jobs} ]]; then num_jobs=$NUM_PROCESSORS fi + #if [[ "$TRAVIS" == "true" ]] && (( "$num_jobs" > 1)); then + # num_jobs=$((num_jobs - 1)) + #fi fi echo "using toolset: $CC" @@ -137,7 +142,7 @@ if [[ $VARIANT == "coverage" ]]; then lcov --no-external -c -i -d . -o baseline.info > /dev/null # Perform test - if [[ $MAIN_BRANCH == "1" ]]; then + if [[ $MAIN_BRANCH == "1" && "$DO_VALGRIND" = true ]]; then run_tests_with_valgrind # skip slow autobahn tests #run_autobahn_test_suite @@ -156,10 +161,14 @@ if [[ $VARIANT == "coverage" ]]; then lcov -e "lcov-all.info" "$PWD/include/beast/*" -o lcov.info > /dev/null ~/.local/bin/codecov -X gcov - cat lcov.info | node_modules/.bin/coveralls + #cat lcov.info | node_modules/.bin/coveralls # Clean up these stragglers so BOOST_ROOT cache is clean find $BOOST_ROOT/bin.v2 -name "*.gcda" | xargs rm -f else - run_tests_with_debugger + if [[ $MAIN_BRANCH == "1" && "$DO_VALGRIND" = true ]]; then + run_tests_with_valgrind + else + run_tests_with_debugger + fi fi diff --git a/scripts/field.txt b/scripts/field.txt new file mode 100644 index 0000000000..9f15ab16c7 --- /dev/null +++ b/scripts/field.txt @@ -0,0 +1,351 @@ +Accept +Accept-Additions +Accept-Charset +Accept-Datetime +Accept-Encoding +Accept-Features +Accept-Language +Accept-Patch +Accept-Post +Accept-Ranges +Access-Control +Access-Control-Allow-Credentials +Access-Control-Allow-Headers +Access-Control-Allow-Methods +Access-Control-Allow-Origin +Access-Control-Max-Age +Access-Control-Request-Headers +Access-Control-Request-Method +Age +A-IM +Allow +ALPN +Also-Control +Alternate-Recipient +Alternates +Alt-Svc +Alt-Used +Apparently-To +Apply-To-Redirect-Ref +Approved +Archive +Archived-At +Article-Names +Article-Updates +Authentication-Control +Authentication-Info +Authentication-Results +Authorization +Autoforwarded +Autosubmitted +Auto-Submitted +Base +Bcc +Body +Cache-Control +CalDAV-Timezones +Cancel-Key +Cancel-Lock +Cc +C-Ext +Close +C-Man +Comments +Compliance +Connection +Content-Alternative +Content-Base +Content-Description +Content-Disposition +Content-Duration +Content-Encoding +Content-features +Content-ID +Content-Identifier +Content-Language +Content-Length +Content-Location +Content-MD5 +Content-Range +Content-Return +Content-Script-Type +Content-Style-Type +Content-Transfer-Encoding +Content-Type +Content-Version +Control +Conversion +Conversion-With-Loss +Cookie +Cookie2 +C-Opt +Cost +C-PEP +C-PEP-Info +DASL +Date +Date-Received +DAV +Default-Style +Deferred-Delivery +Delivery-Date +Delta-Base +Depth +Derived-From +Destination +Differential-ID +Digest +Discarded-X400-IPMS-Extensions +Discarded-X400-MTS-Extensions +Disclose-Recipients +Disposition-Notification-Options +Disposition-Notification-To +Distribution +DKIM-Signature +DL-Expansion-History +Downgraded-Bcc +Downgraded-Cc +Downgraded-Disposition-Notification-To +Downgraded-Final-Recipient +Downgraded-From +Downgraded-In-Reply-To +Downgraded-Mail-From +Downgraded-Message-Id +Downgraded-Original-Recipient +Downgraded-Rcpt-To +Downgraded-References +Downgraded-Reply-To +Downgraded-Resent-Bcc +Downgraded-Resent-Cc +Downgraded-Resent-From +Downgraded-Resent-Reply-To +Downgraded-Resent-Sender +Downgraded-Resent-To +Downgraded-Return-Path +Downgraded-Sender +Downgraded-To +EDIINT-Features +Eesst-Version +Encoding +Encrypted +Errors-To +ETag +Expect +Expires +Expiry-Date +Ext +Followup-To +Forwarded +From +Generate-Delivery-Report +GetProfile +Hobareg +Host +HTTP2-Settings +If +If-Match +If-Modified-Since +If-None-Match +If-Range +If-Schedule-Tag-Match +If-Unmodified-Since +IM +Importance +Incomplete-Copy +Injection-Date +Injection-Info +In-Reply-To +Jabber-ID +Keep-Alive +Keywords +Label +Language +Last-Modified +Latest-Delivery-Time +Lines +Link +List-Archive +List-Help +List-ID +List-Owner +List-Post +List-Subscribe +List-Unsubscribe +List-Unsubscribe-Post +Location +Lock-Token +Man +Max-Forwards +Memento-Datetime +Message-Context +Message-ID +Message-Type +Meter +Method-Check +Method-Check-Expires +MIME-Version +MMHS-Acp127-Message-Identifier +MMHS-Authorizing-Users +MMHS-Codress-Message-Indicator +MMHS-Copy-Precedence +MMHS-Exempted-Address +MMHS-Extended-Authorisation-Info +MMHS-Handling-Instructions +MMHS-Message-Instructions +MMHS-Message-Type +MMHS-Originator-PLAD +MMHS-Originator-Reference +MMHS-Other-Recipients-Indicator-CC +MMHS-Other-Recipients-Indicator-To +MMHS-Primary-Precedence +MMHS-Subject-Indicator-Codes +MT-Priority +Negotiate +Newsgroups +NNTP-Posting-Date +NNTP-Posting-Host +Non-Compliance +Obsoletes +Opt +Optional +Optional-WWW-Authenticate +Ordering-Type +Organization +Origin +Original-Encoded-Information-Types +Original-From +Original-Message-ID +Original-Recipient +Original-Sender +Original-Subject +Originator-Return-Address +Overwrite +P3P +Path +PEP +Pep-Info +PICS-Label +Position +Posting-Version +Pragma +Prefer +Preference-Applied +Prevent-NonDelivery-Report +Priority +Privicon +ProfileObject +Protocol +Protocol-Info +Protocol-Query +Protocol-Request +Proxy-Authenticate +Proxy-Authentication-Info +Proxy-Authorization +Proxy-Connection +Proxy-Features +Proxy-Instruction +Public +Public-Key-Pins +Public-Key-Pins-Report-Only +Range +Received +Received-SPF +Redirect-Ref +References +Referer +Referer-Root +Relay-Version +Reply-By +Reply-To +Require-Recipient-Valid-Since +Resent-Bcc +Resent-Cc +Resent-Date +Resent-From +Resent-Message-ID +Resent-Reply-To +Resent-Sender +Resent-To +Resolution-Hint +Resolver-Location +Retry-After +Return-Path +Safe +Schedule-Reply +Schedule-Tag +Security-Scheme +Sec-WebSocket-Accept +Sec-WebSocket-Extensions +Sec-WebSocket-Key +Sec-WebSocket-Protocol +Sec-WebSocket-Version +See-Also +Sender +Sensitivity +Server +Set-Cookie +Set-Cookie2 +SetProfile +SIO-Label +SIO-Label-History +SLUG +SoapAction +Solicitation +Status-URI +Strict-Transport-Security +Subject +SubOK +Subst +Summary +Supersedes +Surrogate-Capability +Surrogate-Control +TCN +TE +Timeout +Title +To +Topic +Trailer +Transfer-Encoding +TTL +UA-Color +UA-Media +UA-Pixels +UA-Resolution +UA-Windowpixels +Upgrade +Urgency +URI +User-Agent +Variant-Vary +Vary +VBR-Info +Version +Via +Want-Digest +Warning +WWW-Authenticate +X400-Content-Identifier +X400-Content-Return +X400-Content-Type +X400-MTS-Identifier +X400-Originator +X400-Received +X400-Recipients +X400-Trace +X-Archived-At +X-Device-Accept +X-Device-Accept-Charset +X-Device-Accept-Encoding +X-Device-Accept-Language +X-Device-User-Agent +X-Frame-Options +X-Mittente +X-PGP-Sig +Xref +X-Ricevuta +X-Riferimento-Message-ID +X-TipoRicevuta +X-Trasporto +X-VerificaSicurezza diff --git a/scripts/install-boost.sh b/scripts/install-boost.sh index 8365c2d544..fcee32d5ab 100755 --- a/scripts/install-boost.sh +++ b/scripts/install-boost.sh @@ -7,8 +7,8 @@ # When testing you can force a boost build by clearing travis caches: # https://travis-ci.org/ripple/rippled/caches set -eu -if [ ! -d "$BOOST_ROOT/lib" ] -then +#if [ ! -d "$BOOST_ROOT" ] +#then wget $BOOST_URL -O /tmp/boost.tar.gz cd `dirname $BOOST_ROOT` rm -fr ${BOOST_ROOT} @@ -21,7 +21,7 @@ then ./bootstrap.sh --prefix=$BOOST_ROOT && \ ./b2 -d1 $params && \ ./b2 -d0 $params install -else - echo "Using cached boost at $BOOST_ROOT" -fi +#else +# echo "Using cached boost at $BOOST_ROOT" +#fi diff --git a/scripts/install-dependencies.sh b/scripts/install-dependencies.sh index 4f0b7cb722..d6b31dd3dc 100755 --- a/scripts/install-dependencies.sh +++ b/scripts/install-dependencies.sh @@ -34,7 +34,7 @@ if [[ ! -x cmake/bin/cmake && -d cmake ]]; then rm -fr cmake fi if [[ ! -d cmake && ${BUILD_SYSTEM:-} == cmake ]]; then - CMAKE_URL="http://www.cmake.org/files/v3.5/cmake-3.5.2-Linux-x86_64.tar.gz" + CMAKE_URL="http://www.cmake.org/files/v3.8/cmake-3.8.0-Linux-x86_64.tar.gz" mkdir cmake && wget --no-check-certificate -O - ${CMAKE_URL} | tar --strip-components=1 -xz -C cmake fi @@ -54,6 +54,7 @@ ls -lah ~/.npm || mkdir ~/.npm # Make sure we own it chown -Rc $USER ~/.npm # We use this so we can filter the subtrees from our coverage report +pip install --user requests==2.13.0 pip install --user https://github.com/codecov/codecov-python/archive/master.zip pip install --user autobahntestsuite @@ -70,6 +71,6 @@ mkdir -p $LCOV_ROOT cd $HOME/lcov-1.12 && make install PREFIX=$LCOV_ROOT # Install coveralls reporter -cd $HERE -mkdir -p node_modules -npm install coveralls +#cd $HERE +#mkdir -p node_modules +#npm install coveralls diff --git a/scripts/make_field.sh b/scripts/make_field.sh new file mode 100644 index 0000000000..8e54c13720 --- /dev/null +++ b/scripts/make_field.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +export LC_COLLATE=C + +echo "// string constants" +echo ' "",' +cat $1 | sort -f | uniq | sed 's/^/ \"/; s/$/\",/' +echo + +echo "enum class field : unsigned short" +echo "{" +echo " unknown = 0," +echo +#cat $1 | uniq | sort -f | sed 's/./\L&/g; s/^/\t/; s/$/,/' +cat $1 | sort -f | uniq | sed 's/\(.*\)/ \L\1,/; s/-/_/g' +echo "};" +echo + +echo "// pairs" +#cat $1 | uniq | sort -f | sed 's/\(.*\)/\tmatch\(field::\L\1, \"\E\1\"\);/; s/-/_/' +cat $1 | sort -f | uniq | perl -nE 'chomp; $a=lc($_); $a=~s/-/_/g; say " match(field::$a, \"$_\");";' | tr -d "\015" + diff --git a/scripts/valgrind.supp b/scripts/valgrind.supp index 8ad196a389..1d1ddc0b54 100644 --- a/scripts/valgrind.supp +++ b/scripts/valgrind.supp @@ -8,3 +8,22 @@ Memcheck:Cond fun:_ZN5beast4zlib6detail14deflate_stream11fill_windowIvEEvRNS0_8z_paramsE } +{ + Ignore OpenSSL malloc + Memcheck:Leak + fun:malloc + fun:CRYPTO_malloc + obj:*libcrypto* +} +{ + Ignore OpenSSL realloc + Memcheck:Leak + fun:realloc + fun:CRYPTO_realloc + obj:*libcrypto* +} +{ + OpenSSL Non-Purify + Memcheck:Cond + obj:*libcrypto* +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ff15cb4d4f..d939ddbd06 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,27 +1,38 @@ # Part of Beast -GroupSources(extras/beast extras) -GroupSources(include/beast beast) -GroupSources(test "/") +add_subdirectory (core) +add_subdirectory (http) +add_subdirectory (websocket) +add_subdirectory (zlib) -add_executable (lib-tests - ${BEAST_INCLUDES} - ${EXTRAS_INCLUDES} - ../extras/beast/unit_test/main.cpp - config.cpp - core.cpp - http.cpp - version.cpp - websocket.cpp - zlib.cpp -) +if ((NOT "${VARIANT}" STREQUAL "coverage") AND + (NOT "${VARIANT}" STREQUAL "ubasan")) -if (NOT WIN32) - target_link_libraries(lib-tests ${Boost_LIBRARIES} Threads::Threads) -else() - target_link_libraries(lib-tests ${Boost_LIBRARIES}) -endif() - -if (MINGW) - set_target_properties(lib-tests PROPERTIES COMPILE_FLAGS "-Wa,-mbig-obj") + add_subdirectory (benchmarks) + add_subdirectory (common) + add_subdirectory (server) + + GroupSources(extras/beast extras) + GroupSources(include/beast beast) + + GroupSources(test "/") + + add_executable (lib-tests + ${BEAST_INCLUDES} + ${EXTRAS_INCLUDES} + ../extras/beast/unit_test/main.cpp + config.cpp + core.cpp + exemplars.cpp + http.cpp + version.cpp + websocket.cpp + zlib.cpp + ) + + target_link_libraries(lib-tests Beast ${Boost_PROGRAM_OPTIONS_LIBRARY}) + + if (MINGW) + set_target_properties(lib-tests PROPERTIES COMPILE_FLAGS "-Wa,-mbig-obj") + endif() endif() diff --git a/test/Jamfile b/test/Jamfile index 018ec76f7f..01d339cc18 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -7,97 +7,31 @@ import os ; -compile config.cpp : : ; -compile core.cpp : : ; -compile http.cpp : : ; -compile version.cpp : : ; -compile websocket.cpp : : ; -compile zlib.cpp : : ; +compile config.cpp : coverage:no + ubasan:no : ; -unit-test core-tests : - ../extras/beast/unit_test/main.cpp - core/async_completion.cpp - core/bind_handler.cpp - core/buffer_cat.cpp - core/buffer_concepts.cpp - core/buffers_adapter.cpp - core/clamp.cpp - core/consuming_buffers.cpp - core/dynabuf_readstream.cpp - core/error.cpp - core/handler_alloc.cpp - core/handler_concepts.cpp - core/handler_ptr.cpp - core/placeholders.cpp - core/prepare_buffer.cpp - core/prepare_buffers.cpp - core/static_streambuf.cpp - core/static_string.cpp - core/stream_concepts.cpp - core/streambuf.cpp - core/to_string.cpp - core/write_dynabuf.cpp - core/base64.cpp - core/empty_base_optimization.cpp - core/get_lowest_layer.cpp - core/is_call_possible.cpp - core/sha1.cpp - ; +compile core.cpp : coverage:no + ubasan:no : ; -unit-test http-tests : - ../extras/beast/unit_test/main.cpp - http/basic_dynabuf_body.cpp - http/basic_fields.cpp - http/basic_parser_v1.cpp - http/concepts.cpp - http/empty_body.cpp - http/fields.cpp - http/header_parser_v1.cpp - http/message.cpp - http/parse.cpp - http/parse_error.cpp - http/parser_v1.cpp - http/read.cpp - http/reason.cpp - http/rfc7230.cpp - http/streambuf_body.cpp - http/string_body.cpp - http/write.cpp - http/chunk_encode.cpp - ; +compile exemplars.cpp : coverage:no + ubasan:no : ; -unit-test bench-tests : - ../extras/beast/unit_test/main.cpp - http/nodejs_parser.cpp - http/parser_bench.cpp - ; +compile http.cpp : coverage:no + ubasan:no : ; -unit-test websocket-tests : - ../extras/beast/unit_test/main.cpp - websocket/error.cpp - websocket/option.cpp - websocket/rfc6455.cpp - websocket/stream.cpp - websocket/teardown.cpp - websocket/frame.cpp - websocket/mask.cpp - websocket/utf8_checker.cpp - ; +compile version.cpp : coverage:no + ubasan:no : ; -unit-test zlib-tests : - ../extras/beast/unit_test/main.cpp - zlib/zlib-1.2.8/adler32.c - zlib/zlib-1.2.8/compress.c - zlib/zlib-1.2.8/crc32.c - zlib/zlib-1.2.8/deflate.c - zlib/zlib-1.2.8/infback.c - zlib/zlib-1.2.8/inffast.c - zlib/zlib-1.2.8/inflate.c - zlib/zlib-1.2.8/inftrees.c - zlib/zlib-1.2.8/trees.c - zlib/zlib-1.2.8/uncompr.c - zlib/zlib-1.2.8/zutil.c - zlib/deflate_stream.cpp - zlib/error.cpp - zlib/inflate_stream.cpp - ; +compile websocket.cpp : coverage:no + ubasan:no : ; + +compile zlib.cpp : coverage:no + ubasan:no : ; + +build-project benchmarks ; +build-project common ; +build-project core ; +build-project http ; +build-project server ; +build-project websocket ; +build-project zlib ; diff --git a/test/benchmarks/CMakeLists.txt b/test/benchmarks/CMakeLists.txt new file mode 100644 index 0000000000..f6c0afcbc6 --- /dev/null +++ b/test/benchmarks/CMakeLists.txt @@ -0,0 +1,25 @@ +# Part of Beast + +GroupSources(extras/beast extras) +GroupSources(include/beast beast) + +GroupSources(test/benchmarks "/") +GroupSources(test/http "/") + +add_executable (benchmarks + ${BEAST_INCLUDES} + ${EXTRAS_INCLUDES} + ../../extras/beast/unit_test/main.cpp + ../http/message_fuzz.hpp + nodejs_parser.hpp + buffers.cpp + nodejs_parser.cpp + parser.cpp + utf8_checker.cpp +) + +target_link_libraries(benchmarks + Beast + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ) diff --git a/test/benchmarks/Jamfile b/test/benchmarks/Jamfile new file mode 100644 index 0000000000..a3ff3bd6da --- /dev/null +++ b/test/benchmarks/Jamfile @@ -0,0 +1,17 @@ +# +# 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) +# + +unit-test benchmarks : + ../../extras/beast/unit_test/main.cpp + buffers.cpp + nodejs_parser.cpp + parser.cpp + utf8_checker.cpp + : + coverage:no + ubasan:no + ; diff --git a/test/benchmarks/buffers.cpp b/test/benchmarks/buffers.cpp new file mode 100644 index 0000000000..5cc99e6507 --- /dev/null +++ b/test/benchmarks/buffers.cpp @@ -0,0 +1,238 @@ +// +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { + +class buffers_test : public beast::unit_test::suite +{ +public: + using size_type = std::uint64_t; + + class timer + { + using clock_type = + std::chrono::system_clock; + + clock_type::time_point when_; + + public: + using duration = + clock_type::duration; + + timer() + : when_(clock_type::now()) + { + } + + duration + elapsed() const + { + return clock_type::now() - when_; + } + }; + + inline + size_type + throughput(std::chrono::duration< + double> const& elapsed, size_type items) + { + using namespace std::chrono; + return static_cast( + 1 / (elapsed/items).count()); + } + + template + static + std::size_t + fill(MutableBufferSequence const& buffers) + { + std::size_t n = 0; + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + for(boost::asio::mutable_buffer buffer : buffers) + { + std::fill( + buffer_cast(buffer), + buffer_cast(buffer) + + buffer_size(buffer), '\0'); + n += buffer_size(buffer); + } + return n; + } + + template + size_type + do_prepares(std::size_t repeat, + std::size_t count, std::size_t size) + { + timer t; + size_type total = 0; + for(auto i = repeat; i--;) + { + DynamicBuffer b; + for(auto j = count; j--;) + { + auto const n = fill(b.prepare(size)); + b.commit(n); + total += n; + } + } + return throughput(t.elapsed(), total); + } + + template + size_type + do_hints(std::size_t repeat, + std::size_t count, std::size_t size) + { + timer t; + size_type total = 0; + for(auto i = repeat; i--;) + { + DynamicBuffer b; + for(auto j = count; j--;) + { + for(auto remain = size; remain;) + { + auto const n = fill(b.prepare( + read_size(b, remain))); + b.commit(n); + remain -= n; + total += n; + } + } + } + return throughput(t.elapsed(), total); + } + + template + size_type + do_random(std::size_t repeat, + std::size_t count, std::size_t size) + { + timer t; + size_type total = 0; + for(auto i = repeat; i--;) + { + DynamicBuffer b; + for(auto j = count; j--;) + { + auto const n = fill(b.prepare( + 1 + (rand()%(2*size)))); + b.commit(n); + total += n; + } + } + return throughput(t.elapsed(), total); + } + + static + inline + void + do_trials_1(bool) + { + } + + template + void + do_trials_1(bool print, F0&& f, FN... fn) + { + timer t; + using namespace std::chrono; + static size_type constexpr den = 1024 * 1024; + if(print) + { + log << std::right << std::setw(10) << + ((f() + (den / 2)) / den) << " MB/s"; + log.flush(); + } + else + { + f(); + } + do_trials_1(print, fn...); + } + + template + void + do_trials(string_view name, + std::size_t trials, F0&& f0, FN... fn) + { + using namespace std::chrono; + // warm-up + do_trials_1(false, f0, fn...); + do_trials_1(false, f0, fn...); + while(trials--) + { + timer t; + log << std::left << std::setw(24) << name << ":"; + log.flush(); + do_trials_1(true, f0, fn...); + log << " " << + duration_cast(t.elapsed()).count() << "ms"; + log << std::endl; + } + } + + void + run() override + { + static std::size_t constexpr trials = 1; + static std::size_t constexpr repeat = 250; + std::vector> params; + params.emplace_back(1024, 1024); + params.emplace_back(512, 4096); + params.emplace_back(256, 32768); + log << std::endl; + for(auto const& param : params) + { + auto const count = param.first; + auto const size = param.second; + auto const s = std::string("count=") + std::to_string(count) + + ", size=" + std::to_string(size); + log << std::left << std::setw(24) << s << " " << + std::right << std::setw(15) << "prepare" << + std::right << std::setw(15) << "with hint" << + std::right << std::setw(15) << "random" << + std::endl; + do_trials("multi_buffer", trials, + [&](){ return do_prepares(repeat, count, size); } + ,[&](){ return do_hints (repeat, count, size); } + ,[&](){ return do_random (repeat, count, size); } + ); + do_trials("flat_buffer", trials, + [&](){ return do_prepares(repeat, count, size); } + ,[&](){ return do_hints (repeat, count, size); } + ,[&](){ return do_random (repeat, count, size); } + ); + do_trials("boost::asio::streambuf", trials, + [&](){ return do_prepares(repeat, count, size); } + ,[&](){ return do_hints (repeat, count, size); } + ,[&](){ return do_random (repeat, count, size); } + ); + log << std::endl; + } + pass(); + } +}; + +BEAST_DEFINE_TESTSUITE(buffers,benchmarks,beast); + +} // beast diff --git a/test/http/nodejs-parser/AUTHORS b/test/benchmarks/nodejs-parser/AUTHORS similarity index 100% rename from test/http/nodejs-parser/AUTHORS rename to test/benchmarks/nodejs-parser/AUTHORS diff --git a/test/http/nodejs-parser/LICENSE-MIT b/test/benchmarks/nodejs-parser/LICENSE-MIT similarity index 100% rename from test/http/nodejs-parser/LICENSE-MIT rename to test/benchmarks/nodejs-parser/LICENSE-MIT diff --git a/test/http/nodejs-parser/README.md b/test/benchmarks/nodejs-parser/README.md similarity index 100% rename from test/http/nodejs-parser/README.md rename to test/benchmarks/nodejs-parser/README.md diff --git a/test/http/nodejs-parser/http_parser.c b/test/benchmarks/nodejs-parser/http_parser.c similarity index 100% rename from test/http/nodejs-parser/http_parser.c rename to test/benchmarks/nodejs-parser/http_parser.c diff --git a/test/http/nodejs-parser/http_parser.h b/test/benchmarks/nodejs-parser/http_parser.h similarity index 100% rename from test/http/nodejs-parser/http_parser.h rename to test/benchmarks/nodejs-parser/http_parser.h diff --git a/test/http/nodejs_parser.cpp b/test/benchmarks/nodejs_parser.cpp similarity index 64% rename from test/http/nodejs_parser.cpp rename to test/benchmarks/nodejs_parser.cpp index 633872fa92..39bef4542a 100644 --- a/test/http/nodejs_parser.cpp +++ b/test/benchmarks/nodejs_parser.cpp @@ -5,13 +5,25 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifdef _MSC_VER +#if defined(__GNUC__) && (__GNUC__ >= 7) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +#endif + +#include + +#ifdef BOOST_MSVC # pragma warning (push) # pragma warning (disable: 4127) // conditional expression is constant # pragma warning (disable: 4244) // integer conversion, possible loss of data #endif + #include "nodejs-parser/http_parser.c" -#ifdef _MSC_VER + +#ifdef BOOST_MSVC # pragma warning (pop) #endif +#if defined(__GNUC__) && (__GNUC__ >= 7) +#pragma GCC diagnostic pop +#endif diff --git a/test/http/nodejs_parser.hpp b/test/benchmarks/nodejs_parser.hpp similarity index 58% rename from test/http/nodejs_parser.hpp rename to test/benchmarks/nodejs_parser.hpp index 83ee3665ad..043c58349e 100644 --- a/test/http/nodejs_parser.hpp +++ b/test/benchmarks/nodejs_parser.hpp @@ -12,10 +12,11 @@ #include #include -#include #include +#include #include #include +#include #include #include #include @@ -174,7 +175,7 @@ public: error_code ec; auto const used = write(data, size, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); return used; } @@ -189,7 +190,7 @@ public: error_code ec; auto const used = write(buffers, ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); return used; } @@ -204,12 +205,15 @@ public: error_code ec; write_eof(ec); if(ec) - throw system_error{ec}; + BOOST_THROW_EXCEPTION(system_error{ec}); } void write_eof(error_code& ec); + void + check_header(); + private: Derived& impl() @@ -217,231 +221,6 @@ private: return *static_cast(this); } - template - class has_on_start_t - { - template().on_start(), std::true_type{})> - static R check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); - public: - static bool const value = type::value; - }; - template - using has_on_start = - std::integral_constant::value>; - - void - call_on_start(std::true_type) - { - impl().on_start(); - } - - void - call_on_start(std::false_type) - { - } - - template - class has_on_field_t - { - template().on_field( - std::declval(), - std::declval()), - std::true_type{})> - static R check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); - public: - static bool const value = type::value; - }; - template - using has_on_field = - std::integral_constant::value>; - - void - call_on_field(std::string const& field, - std::string const& value, std::true_type) - { - impl().on_field(field, value); - } - - void - call_on_field(std::string const&, std::string const&, - std::false_type) - { - } - - template - class has_on_headers_complete_t - { - template().on_headers_complete( - std::declval()), std::true_type{})> - static R check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); - public: - static bool const value = type::value; - }; - template - using has_on_headers_complete = - std::integral_constant::value>; - - void - call_on_headers_complete(error_code& ec, std::true_type) - { - impl().on_headers_complete(ec); - } - - void - call_on_headers_complete(error_code&, std::false_type) - { - } - - template - class has_on_request_t - { - template().on_request( - std::declval(), std::declval(), - std::declval(), std::declval(), - std::declval(), std::declval()), - std::true_type{})> - static R check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); - public: - static bool const value = type::value; - }; - template - using has_on_request = - std::integral_constant::value>; - - void - call_on_request(unsigned method, std::string url, - int major, int minor, bool keep_alive, bool upgrade, - std::true_type) - { - impl().on_request( - method, url, major, minor, keep_alive, upgrade); - } - - void - call_on_request(unsigned, std::string, int, int, bool, bool, - std::false_type) - { - } - - template - class has_on_response_t - { - template().on_response( - std::declval(), std::declval, - std::declval(), std::declval(), - std::declval(), std::declval()), - std::true_type{})> - static R check(int); - template - static std::false_type check(...); -#if 0 - using type = decltype(check(0)); -#else - // VFALCO Trait seems broken for http::parser - using type = std::true_type; -#endif - public: - static bool const value = type::value; - }; - template - using has_on_response = - std::integral_constant::value>; - - bool - call_on_response(int status, std::string text, - int major, int minor, bool keep_alive, bool upgrade, - std::true_type) - { - return impl().on_response( - status, text, major, minor, keep_alive, upgrade); - } - - bool - call_on_response(int, std::string, int, int, bool, bool, - std::false_type) - { - // VFALCO Certainly incorrect - return true; - } - - template - class has_on_body_t - { - template().on_body( - std::declval(), std::declval(), - std::declval()), std::true_type{})> - static R check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); - public: - static bool const value = type::value; - }; - template - using has_on_body = - std::integral_constant::value>; - - void - call_on_body(void const* data, std::size_t bytes, - error_code& ec, std::true_type) - { - impl().on_body(data, bytes, ec); - } - - void - call_on_body(void const*, std::size_t, - error_code&, std::false_type) - { - } - - template - class has_on_complete_t - { - template().on_complete(), std::true_type{})> - static R check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); - public: - static bool const value = type::value; - }; - template - using has_on_complete = - std::integral_constant::value>; - - void - call_on_complete(std::true_type) - { - impl().on_complete(); - } - - void - call_on_complete(std::false_type) - { - } - - void - check_header(); - static int cb_message_start(http_parser*); static int cb_url(http_parser*, char const*, std::size_t); static int cb_status(http_parser*, char const*, std::size_t); @@ -482,13 +261,13 @@ std::size_t nodejs_basic_parser::write( ConstBufferSequence const& buffers, error_code& ec) { - static_assert(beast::is_ConstBufferSequence< + static_assert(beast::is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); using boost::asio::buffer_cast; using boost::asio::buffer_size; std::size_t bytes_used = 0; - for(auto const& buffer : buffers) + for(boost::asio::const_buffer buffer : buffers) { auto const n = write( buffer_cast(buffer), @@ -525,7 +304,8 @@ nodejs_basic_parser(nodejs_basic_parser&& other) template auto -nodejs_basic_parser::operator=(nodejs_basic_parser&& other) -> +nodejs_basic_parser:: +operator=(nodejs_basic_parser&& other) -> nodejs_basic_parser& { state_ = other.state_; @@ -568,7 +348,8 @@ operator=(nodejs_basic_parser const& other) -> } template -nodejs_basic_parser::nodejs_basic_parser(bool request) noexcept +nodejs_basic_parser:: +nodejs_basic_parser(bool request) noexcept { state_.data = this; http_parser_init(&state_, request @@ -578,7 +359,8 @@ nodejs_basic_parser::nodejs_basic_parser(bool request) noexcept template std::size_t -nodejs_basic_parser::write(void const* data, +nodejs_basic_parser:: +write(void const* data, std::size_t size, error_code& ec) { ec_ = &ec; @@ -595,11 +377,11 @@ nodejs_basic_parser::write(void const* data, template void -nodejs_basic_parser::write_eof(error_code& ec) +nodejs_basic_parser:: +write_eof(error_code& ec) { ec_ = &ec; - http_parser_execute( - &state_, hooks(), nullptr, 0); + http_parser_execute(&state_, hooks(), nullptr, 0); if(! ec) ec = detail::make_nodejs_error( static_cast(state_.http_errno)); @@ -607,13 +389,12 @@ nodejs_basic_parser::write_eof(error_code& ec) template void -nodejs_basic_parser::check_header() +nodejs_basic_parser:: +check_header() { if(! value_.empty()) { - //detail::trim(value_); - call_on_field(field_, value_, - has_on_field{}); + impl().on_field(field_, value_); field_.clear(); value_.clear(); } @@ -621,7 +402,8 @@ nodejs_basic_parser::check_header() template int -nodejs_basic_parser::cb_message_start(http_parser* p) +nodejs_basic_parser:: +cb_message_start(http_parser* p) { auto& t = *reinterpret_cast(p->data); t.complete_ = false; @@ -629,13 +411,14 @@ nodejs_basic_parser::cb_message_start(http_parser* p) t.status_.clear(); t.field_.clear(); t.value_.clear(); - t.call_on_start(has_on_start{}); + t.impl().on_start(); return 0; } template int -nodejs_basic_parser::cb_url(http_parser* p, +nodejs_basic_parser:: +cb_url(http_parser* p, char const* in, std::size_t bytes) { auto& t = *reinterpret_cast(p->data); @@ -645,7 +428,8 @@ nodejs_basic_parser::cb_url(http_parser* p, template int -nodejs_basic_parser::cb_status(http_parser* p, +nodejs_basic_parser:: +cb_status(http_parser* p, char const* in, std::size_t bytes) { auto& t = *reinterpret_cast(p->data); @@ -655,7 +439,8 @@ nodejs_basic_parser::cb_status(http_parser* p, template int -nodejs_basic_parser::cb_header_field(http_parser* p, +nodejs_basic_parser:: +cb_header_field(http_parser* p, char const* in, std::size_t bytes) { auto& t = *reinterpret_cast(p->data); @@ -666,7 +451,8 @@ nodejs_basic_parser::cb_header_field(http_parser* p, template int -nodejs_basic_parser::cb_header_value(http_parser* p, +nodejs_basic_parser:: +cb_header_value(http_parser* p, char const* in, std::size_t bytes) { auto& t = *reinterpret_cast(p->data); @@ -676,62 +462,68 @@ nodejs_basic_parser::cb_header_value(http_parser* p, template int -nodejs_basic_parser::cb_headers_complete(http_parser* p) +nodejs_basic_parser:: +cb_headers_complete(http_parser* p) { auto& t = *reinterpret_cast(p->data); t.check_header(); - t.call_on_headers_complete(*t.ec_, - has_on_headers_complete{}); + t.impl().on_headers_complete(*t.ec_); if(*t.ec_) return 1; bool const keep_alive = http_should_keep_alive(p) != 0; if(p->type == http_parser_type::HTTP_REQUEST) { - t.call_on_request(p->method, t.url_, + t.impl().on_request(p->method, t.url_, p->http_major, p->http_minor, keep_alive, - p->upgrade, has_on_request{}); + p->upgrade); return 0; } - return t.call_on_response(p->status_code, t.status_, + return t.impl().on_response(p->status_code, t.status_, p->http_major, p->http_minor, keep_alive, - p->upgrade, has_on_response{}) ? 0 : 1; + p->upgrade) ? 0 : 1; } template int -nodejs_basic_parser::cb_body(http_parser* p, +nodejs_basic_parser:: +cb_body(http_parser* p, char const* in, std::size_t bytes) { auto& t = *reinterpret_cast(p->data); - t.call_on_body(in, bytes, *t.ec_, has_on_body{}); + t.impl().on_body(in, bytes, *t.ec_); return *t.ec_ ? 1 : 0; } template int -nodejs_basic_parser::cb_message_complete(http_parser* p) +nodejs_basic_parser:: +cb_message_complete(http_parser* p) { auto& t = *reinterpret_cast(p->data); t.complete_ = true; - t.call_on_complete(has_on_complete{}); + t.impl().on_complete(); return 0; } template int -nodejs_basic_parser::cb_chunk_header(http_parser*) +nodejs_basic_parser:: +cb_chunk_header(http_parser*) { return 0; } template int -nodejs_basic_parser::cb_chunk_complete(http_parser*) +nodejs_basic_parser:: +cb_chunk_complete(http_parser*) { return 0; } +//------------------------------------------------------------------------------ + /** A HTTP parser. The parser may only be used once. @@ -740,11 +532,6 @@ template class nodejs_parser : public nodejs_basic_parser> { - using message_type = - message; - - message_type m_; - typename message_type::body_type::reader r_; bool started_ = false; public: @@ -752,7 +539,6 @@ public: nodejs_parser() : http::nodejs_basic_parser(isRequest) - , r_(m_) { } @@ -763,12 +549,6 @@ public: return started_; } - message_type - release() - { - return std::move(m_); - } - private: friend class http::nodejs_basic_parser; @@ -779,35 +559,30 @@ private: } void - on_field(std::string const& field, std::string const& value) + on_field(std::string const&, std::string const&) { - m_.fields.insert(field, value); } void - on_headers_complete(error_code&) + on_headers_complete(error_code& ec) { // vFALCO TODO Decode the Content-Length and // Transfer-Encoding, see if we can reserve the buffer. // // r_.reserve(content_length) + ec.assign(0, ec.category()); } bool - on_request(unsigned method, std::string const& url, - int major, int minor, bool /*keep_alive*/, bool /*upgrade*/, - std::true_type) + on_request(unsigned, std::string const&, + int, int, bool, bool, std::true_type) { - m_.method = detail::method_to_string(method); - m_.url = url; - m_.version = major * 10 + minor; return true; } bool on_request(unsigned, std::string const&, - int, int, bool, bool, - std::false_type) + int, int, bool, bool, std::false_type) { return true; } @@ -819,19 +594,13 @@ private: return on_request(method, url, major, minor, keep_alive, upgrade, std::integral_constant< - bool, message_type::is_request>{}); + bool, isRequest>{}); } bool - on_response(int status, std::string const& reason, - int major, int minor, bool keep_alive, bool upgrade, - std::true_type) + on_response(int, std::string const&, + int, int, bool, bool, std::true_type) { - beast::detail::ignore_unused(keep_alive, upgrade); - m_.status = status; - m_.reason = reason; - m_.version = major * 10 + minor; - // VFALCO TODO return expect_body_ return true; } @@ -848,14 +617,13 @@ private: { return on_response( status, reason, major, minor, keep_alive, upgrade, - std::integral_constant{}); + std::integral_constant{}); } void - on_body(void const* data, - std::size_t size, error_code& ec) + on_body(void const*, std::size_t, error_code& ec) { - r_.write(data, size, ec); + ec.assign(0, ec.category()); } void diff --git a/test/benchmarks/parser.cpp b/test/benchmarks/parser.cpp new file mode 100644 index 0000000000..b7c67f42be --- /dev/null +++ b/test/benchmarks/parser.cpp @@ -0,0 +1,290 @@ +// +// 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 "nodejs_parser.hpp" +#include "../http/message_fuzz.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +class parser_test : public beast::unit_test::suite +{ +public: + static std::size_t constexpr N = 2000; + + //using corpus = std::vector; + using corpus = std::vector; + + corpus creq_; + corpus cres_; + std::size_t size_ = 0; + + template + static + std::string + to_string(ConstBufferSequence const& bs) + { + return boost::lexical_cast< + std::string>(buffers(bs)); + } + + corpus + build_corpus(std::size_t n, std::true_type) + { + corpus v; + v.resize(n); + message_fuzz mg; + for(std::size_t i = 0; i < n; ++i) + { + mg.request(v[i]); + size_ += v[i].size(); + BEAST_EXPECT(v[i].size() > 0); + } + return v; + } + + corpus + build_corpus(std::size_t n, std::false_type) + { + corpus v; + v.resize(n); + message_fuzz mg; + for(std::size_t i = 0; i < n; ++i) + { + mg.response(v[i]); + size_ += v[i].size(); + BEAST_EXPECT(v[i].size() > 0); + } + return v; + } + + template + static + std::size_t + feed(ConstBufferSequence const& buffers, + basic_parser& parser, + error_code& ec) + { + using boost::asio::buffer_size; + beast::consuming_buffers< + ConstBufferSequence> cb{buffers}; + std::size_t used = 0; + for(;;) + { + auto const n = + parser.put(cb, ec); + if(ec) + return 0; + if(n == 0) + break; + cb.consume(n); + used += n; + if(parser.is_done()) + break; + if(buffer_size(cb) == 0) + break; + } + return used; + } + + template + void + testParser1(std::size_t repeat, corpus const& v) + { + while(repeat--) + for(auto const& b : v) + { + Parser p; + error_code ec; + p.write(b.data(), ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + log << to_string(b.data()) << std::endl; + } + } + + template + void + testParser2(std::size_t repeat, corpus const& v) + { + while(repeat--) + for(auto const& b : v) + { + Parser p; + p.header_limit((std::numeric_limits::max)()); + error_code ec; + feed(b.data(), p, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + log << to_string(b.data()) << std::endl; + } + } + + template + void + timedTest(std::size_t repeat, std::string const& name, Function&& f) + { + using namespace std::chrono; + using clock_type = std::chrono::high_resolution_clock; + log << name << std::endl; + for(std::size_t trial = 1; trial <= repeat; ++trial) + { + auto const t0 = clock_type::now(); + f(); + auto const elapsed = clock_type::now() - t0; + log << + "Trial " << trial << ": " << + duration_cast(elapsed).count() << " ms" << std::endl; + } + } + + template + struct null_parser : + basic_parser> + { + }; + + template + struct bench_parser : basic_parser< + isRequest, bench_parser> + { + using mutable_buffers_type = + boost::asio::mutable_buffers_1; + + void + on_request(verb, string_view, + string_view, int, error_code& ec) + { + ec.assign(0, ec.category()); + } + + void + on_response(int, + string_view, + int, error_code& ec) + { + ec.assign(0, ec.category()); + } + + void + on_field(field, + string_view, string_view, error_code& ec) + { + ec.assign(0, ec.category()); + } + + void + on_header(error_code& ec) + { + ec.assign(0, ec.category()); + } + + void + on_body(boost::optional const&, + error_code& ec) + { + ec.assign(0, ec.category()); + } + + std::size_t + on_data(string_view s, error_code& ec) + { + ec.assign(0, ec.category()); + return s.size(); + } + + void + on_chunk(std::uint64_t, + string_view, + error_code& ec) + { + ec.assign(0, ec.category()); + } + + void + on_complete(error_code& ec) + { + ec.assign(0, ec.category()); + } + }; + + void + testSpeed() + { + static std::size_t constexpr Trials = 5; + static std::size_t constexpr Repeat = 500; + + creq_ = build_corpus(N/2, std::true_type{}); + cres_ = build_corpus(N/2, std::false_type{}); + + log << "sizeof(request parser) == " << + sizeof(null_parser) << '\n'; + + log << "sizeof(response parser) == " << + sizeof(null_parser)<< '\n'; + + testcase << "Parser speed test, " << + ((Repeat * size_ + 512) / 1024) << "KB in " << + (Repeat * (creq_.size() + cres_.size())) << " messages"; + +#if 0 + timedTest(Trials, "http::parser", + [&] + { + testParser2>(Repeat, creq_); + testParser2>(Repeat, cres_); + }); +#endif +#if 1 + timedTest(Trials, "http::basic_parser", + [&] + { + testParser2 >( + Repeat, creq_); + testParser2>( + Repeat, cres_); + }); +#if 1 + timedTest(Trials, "nodejs_parser", + [&] + { + testParser1>( + Repeat, creq_); + testParser1>( + Repeat, cres_); + }); +#endif +#endif + pass(); + } + + void run() override + { + pass(); + testSpeed(); + } +}; + +BEAST_DEFINE_TESTSUITE(parser,benchmarks,beast); + +} // http +} // beast + diff --git a/test/benchmarks/utf8_checker.cpp b/test/benchmarks/utf8_checker.cpp new file mode 100644 index 0000000000..bdb8c5da5b --- /dev/null +++ b/test/benchmarks/utf8_checker.cpp @@ -0,0 +1,140 @@ +// +// 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 +#include +#include +#include +#include + +namespace beast { + +class utf8_checker_test : public beast::unit_test::suite +{ + std::mt19937 rng_; + +public: + using size_type = std::uint64_t; + + class timer + { + using clock_type = + std::chrono::system_clock; + + clock_type::time_point when_; + + public: + using duration = + clock_type::duration; + + timer() + : when_(clock_type::now()) + { + } + + duration + elapsed() const + { + return clock_type::now() - when_; + } + }; + + template + UInt + rand(std::size_t n) + { + return static_cast( + std::uniform_int_distribution< + std::size_t>{0, n-1}(rng_)); + } + + static + inline + size_type + throughput(std::chrono::duration< + double> const& elapsed, size_type items) + { + using namespace std::chrono; + return static_cast( + 1 / (elapsed/items).count()); + } + + std::string + corpus(std::size_t n) + { + std::string s; + s.reserve(n); + while(n--) + s.push_back(static_cast( + ' ' + rand(95))); + return s; + } + + void + checkLocale(std::string const& s) + { + using namespace boost::locale; + auto p = s.begin(); + auto const e = s.end(); + while(p != e) + { + auto cp = utf::utf_traits::decode(p, e); + if(cp == utf::illegal) + break; + } + } + + void + checkBeast(std::string const& s) + { + beast::websocket::detail::check_utf8( + s.data(), s.size()); + } + + template + typename timer::clock_type::duration + test(F const& f) + { + timer t; + f(); + return t.elapsed(); + } + + void + run() override + { + auto const s = corpus(32 * 1024 * 1024); + for(int i = 0; i < 5; ++ i) + { + auto const elapsed = test([&]{ + checkBeast(s); + checkBeast(s); + checkBeast(s); + checkBeast(s); + checkBeast(s); + }); + log << "beast: " << throughput(elapsed, s.size()) << " char/s" << std::endl; + } + for(int i = 0; i < 5; ++ i) + { + auto const elapsed = test([&]{ + checkLocale(s); + checkLocale(s); + checkLocale(s); + checkLocale(s); + checkLocale(s); + }); + log << "locale: " << throughput(elapsed, s.size()) << " char/s" << std::endl; + } + pass(); + } +}; + +BEAST_DEFINE_TESTSUITE(utf8_checker,benchmarks,beast); + +} // beast + diff --git a/test/common/CMakeLists.txt b/test/common/CMakeLists.txt new file mode 100644 index 0000000000..651732cb6b --- /dev/null +++ b/test/common/CMakeLists.txt @@ -0,0 +1,27 @@ +# Part of Beast + +GroupSources(example/common common) +GroupSources(extras/beast extras) +GroupSources(include/beast beast) +GroupSources(test/common "/") + +add_executable (common-test + ${BEAST_INCLUDES} + ${COMMON_INCLUDES} + detect_ssl.cpp + mime_types.cpp + rfc7231.cpp + ssl_stream.cpp + write_msg.cpp + main.cpp +) + +target_link_libraries(common-test + Beast + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ) + +if (OPENSSL_FOUND) + target_link_libraries (common-test ${OPENSSL_LIBRARIES}) +endif() diff --git a/test/common/Jamfile b/test/common/Jamfile new file mode 100644 index 0000000000..282b842be0 --- /dev/null +++ b/test/common/Jamfile @@ -0,0 +1,18 @@ +# +# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +exe common-test : + detect_ssl.cpp + mime_types.cpp + rfc7231.cpp + ssl_stream.cpp + write_msg.cpp + main.cpp + : + coverage:no + ubasan:no + ; diff --git a/test/core/async_completion.cpp b/test/common/detect_ssl.cpp similarity index 85% rename from test/core/async_completion.cpp rename to test/common/detect_ssl.cpp index 15e0d7dbcc..00fced9f25 100644 --- a/test/core/async_completion.cpp +++ b/test/common/detect_ssl.cpp @@ -6,4 +6,5 @@ // // Test that header file is self-contained. -#include +#include "../../example/common/detect_ssl.hpp" + diff --git a/test/common/main.cpp b/test/common/main.cpp new file mode 100644 index 0000000000..6335cbf20a --- /dev/null +++ b/test/common/main.cpp @@ -0,0 +1,10 @@ +// +// 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) +// + +int main() +{ +} diff --git a/test/common/mime_types.cpp b/test/common/mime_types.cpp new file mode 100644 index 0000000000..a681a902c8 --- /dev/null +++ b/test/common/mime_types.cpp @@ -0,0 +1,10 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include "../../example/common/mime_types.hpp" + diff --git a/test/http/basic_dynabuf_body.cpp b/test/common/rfc7231.cpp similarity index 86% rename from test/http/basic_dynabuf_body.cpp rename to test/common/rfc7231.cpp index a8a449c217..8c5c017ca7 100644 --- a/test/http/basic_dynabuf_body.cpp +++ b/test/common/rfc7231.cpp @@ -6,4 +6,5 @@ // // Test that header file is self-contained. -#include +#include "../../example/common/rfc7231.hpp" + diff --git a/test/common/ssl_stream.cpp b/test/common/ssl_stream.cpp new file mode 100644 index 0000000000..6a54bdbdb6 --- /dev/null +++ b/test/common/ssl_stream.cpp @@ -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) +// + +#if BEAST_USE_OPENSSL + +// Test that header file is self-contained. +#include "../../example/common/ssl_stream.hpp" + +#endif diff --git a/test/core/prepare_buffer.cpp b/test/common/write_msg.cpp similarity index 85% rename from test/core/prepare_buffer.cpp rename to test/common/write_msg.cpp index 989ce0c4d3..80d62d68de 100644 --- a/test/core/prepare_buffer.cpp +++ b/test/common/write_msg.cpp @@ -6,4 +6,5 @@ // // Test that header file is self-contained. -#include +#include "../../example/common/write_msg.hpp" + diff --git a/test/core/CMakeLists.txt b/test/core/CMakeLists.txt index 766627e3ec..39749c57e0 100644 --- a/test/core/CMakeLists.txt +++ b/test/core/CMakeLists.txt @@ -1,44 +1,56 @@ # Part of Beast +GroupSources(example example) GroupSources(extras/beast extras) GroupSources(include/beast beast) + GroupSources(test/core "/") add_executable (core-tests ${BEAST_INCLUDES} + ${EXAMPLE_INCLUDES} ${EXTRAS_INCLUDES} ../../extras/beast/unit_test/main.cpp buffer_test.hpp - async_completion.cpp + file_test.hpp + async_result.cpp bind_handler.cpp buffer_cat.cpp - buffer_concepts.cpp + buffer_prefix.cpp + buffered_read_stream.cpp buffers_adapter.cpp clamp.cpp consuming_buffers.cpp - dynabuf_readstream.cpp + doc_examples.cpp + doc_snippets.cpp + drain_buffer.cpp error.cpp + file.cpp + file_posix.cpp + file_stdio.cpp + file_win32.cpp + flat_buffer.cpp handler_alloc.cpp - handler_concepts.cpp handler_ptr.cpp - placeholders.cpp - prepare_buffer.cpp - prepare_buffers.cpp - static_streambuf.cpp + multi_buffer.cpp + ostream.cpp + read_size.cpp + span.cpp + static_buffer.cpp static_string.cpp - stream_concepts.cpp - streambuf.cpp - to_string.cpp - write_dynabuf.cpp + string.cpp + string_param.cpp + type_traits.cpp base64.cpp empty_base_optimization.cpp - get_lowest_layer.cpp - is_call_possible.cpp sha1.cpp ) -if (NOT WIN32) - target_link_libraries(core-tests ${Boost_LIBRARIES} Threads::Threads) -else() - target_link_libraries(core-tests ${Boost_LIBRARIES}) -endif() +target_link_libraries(core-tests + Beast + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_COROUTINE_LIBRARY} + ${Boost_THREAD_LIBRARY} + ${Boost_CONTEXT_LIBRARY} + ) diff --git a/test/core/Jamfile b/test/core/Jamfile new file mode 100644 index 0000000000..159b7ae568 --- /dev/null +++ b/test/core/Jamfile @@ -0,0 +1,41 @@ +# +# 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) +# + +unit-test core-tests : + ../../extras/beast/unit_test/main.cpp + async_result.cpp + bind_handler.cpp + buffer_cat.cpp + buffer_prefix.cpp + buffered_read_stream.cpp + buffers_adapter.cpp + clamp.cpp + consuming_buffers.cpp + doc_examples.cpp + doc_snippets.cpp + drain_buffer.cpp + error.cpp + file.cpp + file_posix.cpp + file_stdio.cpp + file_win32.cpp + flat_buffer.cpp + handler_alloc.cpp + handler_ptr.cpp + multi_buffer.cpp + ostream.cpp + read_size.cpp + span.cpp + static_buffer.cpp + static_string.cpp + string.cpp + string_param.cpp + type_traits.cpp + base64.cpp + empty_base_optimization.cpp + sha1.cpp + ; diff --git a/test/core/async_result.cpp b/test/core/async_result.cpp new file mode 100644 index 0000000000..1aca1cbed8 --- /dev/null +++ b/test/core/async_result.cpp @@ -0,0 +1,38 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include + +#include +#include +#include + +namespace beast { +namespace { + +struct handler +{ + void operator()(beast::error_code, std::size_t) const; +}; + +static_assert(detail::is_invocable< + typename async_result::completion_handler_type, + void(error_code, std::size_t)>::value, ""); + +static_assert(std::is_same::return_type>::value, ""); + +static_assert(std::is_constructible< + async_result, + typename async_result::completion_handler_type&>::value, ""); + +} // (anon-ns) +} // beast diff --git a/test/core/base64.cpp b/test/core/base64.cpp index 606f8b13ea..e33f65d67d 100644 --- a/test/core/base64.cpp +++ b/test/core/base64.cpp @@ -34,6 +34,19 @@ public: check ("foob", "Zm9vYg=="); check ("fooba", "Zm9vYmE="); check ("foobar", "Zm9vYmFy"); + + check( + "Man is distinguished, not only by his reason, but by this singular passion from " + "other animals, which is a lust of the mind, that by a perseverance of delight " + "in the continued and indefatigable generation of knowledge, exceeds the short " + "vehemence of any carnal pleasure." + , + "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz" + "IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg" + "dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu" + "dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo" + "ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=" + ); } }; diff --git a/test/core/bind_handler.cpp b/test/core/bind_handler.cpp index 9299cb9e95..85486325a1 100644 --- a/test/core/bind_handler.cpp +++ b/test/core/bind_handler.cpp @@ -8,6 +8,7 @@ // Test that header file is self-contained. #include +#include #include #include @@ -16,6 +17,21 @@ namespace beast { class bind_handler_test : public unit_test::suite { public: + struct handler + { + void + operator()() const; + }; + +#if 0 + // This function should fail to compile + void + failStdBind() + { + std::bind(bind_handler(handler{})); + } +#endif + void callback(int v) { diff --git a/test/core/buffer_cat.cpp b/test/core/buffer_cat.cpp index 688b315c87..88005dc558 100644 --- a/test/core/buffer_cat.cpp +++ b/test/core/buffer_cat.cpp @@ -165,6 +165,12 @@ public: } // decrement iterator + /* VFALCO + This causes a mysterious "uninitialized variable" + warning related to this function (see comment) + https://code.woboq.org/qt5/include/c++/6.3.1/bits/stl_iterator.h.html#159 + */ +#if 0 { auto const rbegin = make_reverse_iterator(bs.end()); @@ -175,6 +181,7 @@ public: n += buffer_size(*it); BEAST_EXPECT(n == 9); } +#endif try { @@ -215,51 +222,39 @@ public: { }; - // Check is_all_ConstBufferSequence - static_assert( - detail::is_all_ConstBufferSequence< - const_buffers_1 - >::value, ""); - static_assert( - detail::is_all_ConstBufferSequence< - const_buffers_1, const_buffers_1 - >::value, ""); - static_assert( - detail::is_all_ConstBufferSequence< - mutable_buffers_1 - >::value, ""); - static_assert( - detail::is_all_ConstBufferSequence< - mutable_buffers_1, mutable_buffers_1 - >::value, ""); - static_assert( - detail::is_all_ConstBufferSequence< - const_buffers_1, mutable_buffers_1 - >::value, ""); - static_assert( - ! detail::is_all_ConstBufferSequence< - const_buffers_1, mutable_buffers_1, int - >::value, ""); + // Check is_all_const_buffer_sequence + BOOST_STATIC_ASSERT( + detail::is_all_const_buffer_sequence::value); + BOOST_STATIC_ASSERT( + detail::is_all_const_buffer_sequence::value); + BOOST_STATIC_ASSERT( + detail::is_all_const_buffer_sequence::value); + BOOST_STATIC_ASSERT( + detail::is_all_const_buffer_sequence::value); + BOOST_STATIC_ASSERT( + detail::is_all_const_buffer_sequence::value); + BOOST_STATIC_ASSERT( + ! detail::is_all_const_buffer_sequence::value); // Ensure that concatenating mutable buffer // sequences results in a mutable buffer sequence - static_assert(std::is_same< + BOOST_STATIC_ASSERT(std::is_same< mutable_buffer, decltype(buffer_cat( std::declval(), std::declval(), std::declval() - ))::value_type>::value, ""); + ))::value_type>::value); // Ensure that concatenating mixed buffer // sequences results in a const buffer sequence. - static_assert(std::is_same< + BOOST_STATIC_ASSERT(std::is_same< const_buffer, decltype(buffer_cat( std::declval(), std::declval(), std::declval() - ))::value_type>::value, ""); + ))::value_type>::value); testBufferCat(); testIterators(); diff --git a/test/core/buffer_concepts.cpp b/test/core/buffer_concepts.cpp deleted file mode 100644 index 6a3476f670..0000000000 --- a/test/core/buffer_concepts.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// -// 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) -// - -// Test that header file is self-contained. -#include - -namespace beast { - -namespace { -struct T -{ -}; -} - -static_assert(is_ConstBufferSequence::value, ""); -static_assert(! is_ConstBufferSequence::value, ""); - -static_assert(is_MutableBufferSequence::value, ""); -static_assert(! is_MutableBufferSequence::value, ""); - -} // beast diff --git a/test/core/buffer_prefix.cpp b/test/core/buffer_prefix.cpp new file mode 100644 index 0000000000..4da8687b1b --- /dev/null +++ b/test/core/buffer_prefix.cpp @@ -0,0 +1,197 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include + +#include +#include +#include +#include +#include + +namespace beast { + +BOOST_STATIC_ASSERT( + std::is_same()))>::value); + +BOOST_STATIC_ASSERT( + is_const_buffer_sequence()))>::value); + +BOOST_STATIC_ASSERT( + std::is_same()))>::value); +BOOST_STATIC_ASSERT( + is_mutable_buffer_sequence()))>::value); + +class buffer_prefix_test : public beast::unit_test::suite +{ +public: + template + static + std::size_t + bsize1(ConstBufferSequence const& bs) + { + using boost::asio::buffer_size; + std::size_t n = 0; + for(auto it = bs.begin(); it != bs.end(); ++it) + n += buffer_size(*it); + return n; + } + + template + static + std::size_t + bsize2(ConstBufferSequence const& bs) + { + using boost::asio::buffer_size; + std::size_t n = 0; + for(auto it = bs.begin(); it != bs.end(); it++) + n += buffer_size(*it); + return n; + } + + template + static + std::size_t + bsize3(ConstBufferSequence const& bs) + { + using boost::asio::buffer_size; + std::size_t n = 0; + for(auto it = bs.end(); it != bs.begin();) + n += buffer_size(*--it); + return n; + } + + template + static + std::size_t + bsize4(ConstBufferSequence const& bs) + { + using boost::asio::buffer_size; + std::size_t n = 0; + for(auto it = bs.end(); it != bs.begin();) + { + it--; + n += buffer_size(*it); + } + return n; + } + + template + static + std::string + to_string(ConstBufferSequence const& bs) + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + std::string s; + s.reserve(buffer_size(bs)); + for(boost::asio::const_buffer b : bs) + s.append(buffer_cast(b), + buffer_size(b)); + return s; + } + + template + void testMatrix() + { + using boost::asio::buffer_size; + std::string s = "Hello, world"; + BEAST_EXPECT(s.size() == 12); + for(std::size_t x = 1; x < 4; ++x) { + for(std::size_t y = 1; y < 4; ++y) { + std::size_t z = s.size() - (x + y); + { + std::array bs{{ + BufferType{&s[0], x}, + BufferType{&s[x], y}, + BufferType{&s[x+y], z}}}; + for(std::size_t i = 0; i <= s.size() + 1; ++i) + { + auto pb = buffer_prefix(i, bs); + BEAST_EXPECT(to_string(pb) == s.substr(0, i)); + auto pb2 = pb; + BEAST_EXPECT(to_string(pb2) == to_string(pb)); + pb = buffer_prefix(0, bs); + pb2 = pb; + BEAST_EXPECT(buffer_size(pb2) == 0); + pb2 = buffer_prefix(i, bs); + BEAST_EXPECT(to_string(pb2) == s.substr(0, i)); + } + } + }} + } + + void testNullBuffers() + { + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + using boost::asio::null_buffers; + auto pb0 = buffer_prefix(0, null_buffers{}); + BEAST_EXPECT(buffer_size(pb0) == 0); + auto pb1 = buffer_prefix(1, null_buffers{}); + BEAST_EXPECT(buffer_size(pb1) == 0); + BEAST_EXPECT(buffer_copy(pb0, pb1) == 0); + + using pb_type = decltype(pb0); + consuming_buffers cb(pb0); + BEAST_EXPECT(buffer_size(cb) == 0); + BEAST_EXPECT(buffer_copy(cb, pb1) == 0); + cb.consume(1); + BEAST_EXPECT(buffer_size(cb) == 0); + BEAST_EXPECT(buffer_copy(cb, pb1) == 0); + + auto pbc = buffer_prefix(2, cb); + BEAST_EXPECT(buffer_size(pbc) == 0); + BEAST_EXPECT(buffer_copy(pbc, cb) == 0); + } + + void testIterator() + { + using boost::asio::buffer_size; + using boost::asio::const_buffer; + char b[3]; + std::array bs{{ + const_buffer{&b[0], 1}, + const_buffer{&b[1], 1}, + const_buffer{&b[2], 1}}}; + auto pb = buffer_prefix(2, bs); + BEAST_EXPECT(bsize1(pb) == 2); + BEAST_EXPECT(bsize2(pb) == 2); + BEAST_EXPECT(bsize3(pb) == 2); + BEAST_EXPECT(bsize4(pb) == 2); + std::size_t n = 0; + for(auto it = pb.end(); it != pb.begin(); --it) + { + decltype(pb)::const_iterator it2(std::move(it)); + BEAST_EXPECT(buffer_size(*it2) == 1); + it = std::move(it2); + ++n; + } + BEAST_EXPECT(n == 2); + } + + void run() override + { + testMatrix(); + testMatrix(); + testNullBuffers(); + testIterator(); + } +}; + +BEAST_DEFINE_TESTSUITE(buffer_prefix,core,beast); + +} // beast diff --git a/test/core/buffer_test.hpp b/test/core/buffer_test.hpp index d6160cdcd0..3a05b45996 100644 --- a/test/core/buffer_test.hpp +++ b/test/core/buffer_test.hpp @@ -8,9 +8,13 @@ #ifndef BEAST_TEST_BUFFER_TEST_HPP #define BEAST_TEST_BUFFER_TEST_HPP -#include +#include +#include +#include +#include #include #include +#include #include namespace beast { @@ -18,7 +22,32 @@ namespace test { template typename std::enable_if< - is_ConstBufferSequence::value, + is_const_buffer_sequence::value, +std::string>::type +to_string(ConstBufferSequence const& bs) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + std::string s; + s.reserve(buffer_size(bs)); + for(boost::asio::const_buffer b : bs) + s.append(buffer_cast(b), + buffer_size(b)); + return s; +} + +template +void +write_buffer(DynamicBuffer& b, string_view s) +{ + b.commit(boost::asio::buffer_copy( + b.prepare(s.size()), boost::asio::buffer( + s.data(), s.size()))); +} + +template +typename std::enable_if< + is_const_buffer_sequence::value, std::size_t>::type buffer_count(ConstBufferSequence const& buffers) { @@ -27,7 +56,7 @@ buffer_count(ConstBufferSequence const& buffers) template typename std::enable_if< - is_ConstBufferSequence::value, + is_const_buffer_sequence::value, std::size_t>::type size_pre(ConstBufferSequence const& buffers) { @@ -46,7 +75,7 @@ size_pre(ConstBufferSequence const& buffers) template typename std::enable_if< - is_ConstBufferSequence::value, + is_const_buffer_sequence::value, std::size_t>::type size_post(ConstBufferSequence const& buffers) { @@ -58,7 +87,7 @@ size_post(ConstBufferSequence const& buffers) template typename std::enable_if< - is_ConstBufferSequence::value, + is_const_buffer_sequence::value, std::size_t>::type size_rev_pre(ConstBufferSequence const& buffers) { @@ -70,7 +99,7 @@ size_rev_pre(ConstBufferSequence const& buffers) template typename std::enable_if< - is_ConstBufferSequence::value, + is_const_buffer_sequence::value, std::size_t>::type size_rev_post(ConstBufferSequence const& buffers) { diff --git a/test/core/dynabuf_readstream.cpp b/test/core/buffered_read_stream.cpp similarity index 73% rename from test/core/dynabuf_readstream.cpp rename to test/core/buffered_read_stream.cpp index 1293e52369..81536e1ea1 100644 --- a/test/core/dynabuf_readstream.cpp +++ b/test/core/buffered_read_stream.cpp @@ -6,9 +6,9 @@ // // Test that header file is self-contained. -#include +#include -#include +#include #include #include #include @@ -17,11 +17,11 @@ namespace beast { -class dynabuf_readstream_test +class buffered_read_stream_test : public unit_test::suite , public test::enable_yield_to { - using self = dynabuf_readstream_test; + using self = buffered_read_stream_test; public: void testSpecialMembers() @@ -29,16 +29,16 @@ public: using socket_type = boost::asio::ip::tcp::socket; boost::asio::io_service ios; { - dynabuf_readstream srs(ios); - dynabuf_readstream srs2(std::move(srs)); + buffered_read_stream srs(ios); + buffered_read_stream srs2(std::move(srs)); srs = std::move(srs2); BEAST_EXPECT(&srs.get_io_service() == &ios); BEAST_EXPECT(&srs.get_io_service() == &srs2.get_io_service()); } { socket_type sock(ios); - dynabuf_readstream srs(sock); - dynabuf_readstream srs2(std::move(srs)); + buffered_read_stream srs(sock); + buffered_read_stream srs2(std::move(srs)); } } @@ -55,11 +55,11 @@ public: { test::fail_stream< test::string_istream> fs(n, ios_, ", world!"); - dynabuf_readstream< - decltype(fs)&, streambuf> srs(fs); + buffered_read_stream< + decltype(fs)&, multi_buffer> srs(fs); srs.buffer().commit(buffer_copy( srs.buffer().prepare(5), buffer("Hello", 5))); - error_code ec; + error_code ec = test::error::fail_error; boost::asio::read(srs, buffer(&s[0], s.size()), ec); if(! ec) { @@ -73,12 +73,12 @@ public: { test::fail_stream< test::string_istream> fs(n, ios_, ", world!"); - dynabuf_readstream< - decltype(fs)&, streambuf> srs(fs); + buffered_read_stream< + decltype(fs)&, multi_buffer> srs(fs); srs.capacity(3); srs.buffer().commit(buffer_copy( srs.buffer().prepare(5), buffer("Hello", 5))); - error_code ec; + error_code ec = test::error::fail_error; boost::asio::read(srs, buffer(&s[0], s.size()), ec); if(! ec) { @@ -92,11 +92,11 @@ public: { test::fail_stream< test::string_istream> fs(n, ios_, ", world!"); - dynabuf_readstream< - decltype(fs)&, streambuf> srs(fs); + buffered_read_stream< + decltype(fs)&, multi_buffer> srs(fs); srs.buffer().commit(buffer_copy( srs.buffer().prepare(5), buffer("Hello", 5))); - error_code ec; + error_code ec = test::error::fail_error; boost::asio::async_read( srs, buffer(&s[0], s.size()), do_yield[ec]); if(! ec) @@ -111,12 +111,12 @@ public: { test::fail_stream< test::string_istream> fs(n, ios_, ", world!"); - dynabuf_readstream< - decltype(fs)&, streambuf> srs(fs); + buffered_read_stream< + decltype(fs)&, multi_buffer> srs(fs); srs.capacity(3); srs.buffer().commit(buffer_copy( srs.buffer().prepare(5), buffer("Hello", 5))); - error_code ec; + error_code ec = test::error::fail_error; boost::asio::async_read( srs, buffer(&s[0], s.size()), do_yield[ec]); if(! ec) @@ -132,11 +132,12 @@ public: { testSpecialMembers(); - yield_to(&self::testRead, this); + yield_to([&](yield_context yield){ + testRead(yield);}); } }; -BEAST_DEFINE_TESTSUITE(dynabuf_readstream,core,beast); +BEAST_DEFINE_TESTSUITE(buffered_read_stream,core,beast); } // beast diff --git a/test/core/buffers_adapter.cpp b/test/core/buffers_adapter.cpp index 9f8054c6f4..eff1f2238b 100644 --- a/test/core/buffers_adapter.cpp +++ b/test/core/buffers_adapter.cpp @@ -8,10 +8,13 @@ // Test that header file is self-contained. #include -#include +#include "buffer_test.hpp" +#include +#include #include #include #include +#include #include namespace beast { @@ -24,14 +27,8 @@ public: std::string to_string(ConstBufferSequence const& bs) { - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - std::string s; - s.reserve(buffer_size(bs)); - for(auto const& b : bs) - s.append(buffer_cast(b), - buffer_size(b)); - return s; + return boost::lexical_cast< + std::string>(buffers(bs)); } void testBuffersAdapter() @@ -155,19 +152,19 @@ public: using boost::asio::buffer_size; { using sb_type = boost::asio::streambuf; - sb_type sb; + sb_type b; buffers_adapter< - sb_type::mutable_buffers_type> ba(sb.prepare(3)); + sb_type::mutable_buffers_type> ba(b.prepare(3)); BEAST_EXPECT(buffer_size(ba.prepare(3)) == 3); ba.commit(2); BEAST_EXPECT(buffer_size(ba.data()) == 2); } { - using sb_type = beast::streambuf; - sb_type sb(2); - sb.prepare(3); + using sb_type = beast::multi_buffer; + sb_type b; + b.prepare(3); buffers_adapter< - sb_type::mutable_buffers_type> ba(sb.prepare(8)); + sb_type::mutable_buffers_type> ba(b.prepare(8)); BEAST_EXPECT(buffer_size(ba.prepare(8)) == 8); ba.commit(2); BEAST_EXPECT(buffer_size(ba.data()) == 2); @@ -178,10 +175,22 @@ public: ba.consume(5); } } + + void + testIssue386() + { + using type = boost::asio::streambuf; + type buffer; + buffers_adapter< + type::mutable_buffers_type> ba{buffer.prepare(512)}; + read_size(ba, 1024); + } + void run() override { testBuffersAdapter(); testCommit(); + testIssue386(); } }; diff --git a/test/core/consuming_buffers.cpp b/test/core/consuming_buffers.cpp index d4a1dc4756..64a96770b4 100644 --- a/test/core/consuming_buffers.cpp +++ b/test/core/consuming_buffers.cpp @@ -9,7 +9,9 @@ #include #include "buffer_test.hpp" -#include + +#include +#include #include #include #include @@ -34,6 +36,7 @@ public: bool eq(Buffers1 const& lhs, Buffers2 const& rhs) { + using namespace test; return to_string(lhs) == to_string(rhs); } @@ -47,8 +50,24 @@ public: BEAST_EXPECT(test::size_rev_post(buffers) == n); } - void testMatrix() + void + testMembers() { + char buf[12]; + consuming_buffers< + boost::asio::const_buffers_1> cb1{ + boost::in_place_init, buf, sizeof(buf)}; + consuming_buffers< + boost::asio::const_buffers_1> cb2{ + boost::in_place_init, nullptr, 0}; + cb2 = cb1; + cb1 = std::move(cb2); + } + + void + testMatrix() + { + using namespace test; using boost::asio::buffer; using boost::asio::const_buffer; char buf[12]; @@ -89,8 +108,39 @@ public: } }}}} } + + void + testDefaultCtor() + { + using namespace test; + class test_buffer : public boost::asio::const_buffers_1 + { + public: + test_buffer() + : boost::asio::const_buffers_1("\r\n", 2) + { + } + }; - void testNullBuffers() + consuming_buffers cb; + BEAST_EXPECT(to_string(cb) == "\r\n"); + } + + void + testInPlace() + { + using namespace test; + consuming_buffers> cb( + boost::in_place_init, + boost::asio::const_buffers_1("\r", 1), + boost::asio::const_buffers_1("\n", 1)); + BEAST_EXPECT(to_string(cb) == "\r\n"); + } + + void + testNullBuffers() { using boost::asio::buffer_copy; using boost::asio::buffer_size; @@ -103,7 +153,8 @@ public: BEAST_EXPECT(buffer_copy(cb2, cb) == 0); } - void testIterator() + void + testIterator() { using boost::asio::const_buffer; std::array ba; @@ -116,7 +167,10 @@ public: void run() override { + testMembers(); testMatrix(); + testDefaultCtor(); + testInPlace(); testNullBuffers(); testIterator(); } diff --git a/test/core/doc_examples.cpp b/test/core/doc_examples.cpp new file mode 100644 index 0000000000..ff4d2e4b57 --- /dev/null +++ b/test/core/doc_examples.cpp @@ -0,0 +1,85 @@ +// +// 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 "example/common/detect_ssl.hpp" + +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +class doc_core_samples_test + : public beast::unit_test::suite + , public beast::test::enable_yield_to +{ +public: + void + testDetect() + { + char buf[4]; + buf[0] = 0x16; + buf[1] = 0; + buf[2] = 0; + buf[3] = 0; + BEAST_EXPECT(boost::indeterminate(is_ssl_handshake( + boost::asio::buffer(buf, 0)))); + BEAST_EXPECT(boost::indeterminate(is_ssl_handshake( + boost::asio::buffer(buf, 1)))); + BEAST_EXPECT(boost::indeterminate(is_ssl_handshake( + boost::asio::buffer(buf, 2)))); + BEAST_EXPECT(boost::indeterminate(is_ssl_handshake( + boost::asio::buffer(buf, 3)))); + BEAST_EXPECT(is_ssl_handshake( + boost::asio::buffer(buf, 4))); + buf[0] = 0; + BEAST_EXPECT(! is_ssl_handshake( + boost::asio::buffer(buf, 1))); + } + + void + testRead() + { + { + test::pipe p{ios_}; + ostream(p.server.buffer) << + "\x16***"; + error_code ec; + flat_buffer b; + auto const result = detect_ssl(p.server, b, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(result); + } + yield_to( + [&](yield_context yield) + { + test::pipe p{ios_}; + ostream(p.server.buffer) << + "\x16***"; + error_code ec; + flat_buffer b; + auto const result = async_detect_ssl(p.server, b, yield[ec]); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(result); + }); + } + + void + run() + { + testDetect(); + testRead(); + } +}; + +BEAST_DEFINE_TESTSUITE(doc_core_samples,core,beast); + +} // http +} // beast diff --git a/test/core/doc_snippets.cpp b/test/core/doc_snippets.cpp new file mode 100644 index 0000000000..0fa3e97c28 --- /dev/null +++ b/test/core/doc_snippets.cpp @@ -0,0 +1,65 @@ +// +// 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) +// + +//[snippet_core_1a + +#include +#include +#include +#include + +//] + +using namespace beast; + +namespace doc_core_snippets { + +void fxx() +{ + +//[snippet_core_1b +// +using namespace beast; + +boost::asio::io_service ios; +boost::asio::io_service::work work{ios}; +std::thread t{[&](){ ios.run(); }}; + +error_code ec; +boost::asio::ip::tcp::socket sock{ios}; + +//] + +{ +//[snippet_core_2 + +char const* const host = "www.example.com"; +boost::asio::ip::tcp::resolver r{ios}; +boost::asio::ip::tcp::socket stream{ios}; +boost::asio::connect(stream, r.resolve({host, "http"})); + +// At this point `stream` is a connected to a remote +// host and may be used to perform stream operations. + +//] +} + +} // fxx() + +//[snippet_core_3 + +template +void write_string(SyncWriteStream& stream, string_view s) +{ + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + boost::asio::write(stream, boost::asio::const_buffers_1(s.data(), s.size())); +} + +//] + +} // doc_core_snippets diff --git a/test/core/drain_buffer.cpp b/test/core/drain_buffer.cpp new file mode 100644 index 0000000000..5e7e35e710 --- /dev/null +++ b/test/core/drain_buffer.cpp @@ -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) +// + +// Test that header file is self-contained. +#include + +#include +#include + +namespace beast { + +static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + +class drain_buffer_test : public beast::unit_test::suite +{ +public: + void + run() override + { + using boost::asio::buffer_size; + drain_buffer b; + BEAST_EXPECT(buffer_size(b.prepare(0)) == 0); + BEAST_EXPECT(buffer_size(b.prepare(100)) == 100); + try + { + b.prepare(b.max_size() + 1); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + b.prepare(10); + BEAST_EXPECT(b.size() == 0); + b.commit(10); + BEAST_EXPECT(b.size() == 0); + b.consume(10); + BEAST_EXPECT(b.size() == 0); + b.consume(1000); + BEAST_EXPECT(b.size() == 0); + } +}; + +BEAST_DEFINE_TESTSUITE(drain_buffer,core,beast); + +} // beast diff --git a/test/http/parse.cpp b/test/core/file.cpp similarity index 89% rename from test/http/parse.cpp rename to test/core/file.cpp index b15e9e4362..f483142bcc 100644 --- a/test/http/parse.cpp +++ b/test/core/file.cpp @@ -6,4 +6,4 @@ // // Test that header file is self-contained. -#include +#include diff --git a/test/core/file_posix.cpp b/test/core/file_posix.cpp new file mode 100644 index 0000000000..2a8bd046c8 --- /dev/null +++ b/test/core/file_posix.cpp @@ -0,0 +1,35 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#if BEAST_USE_POSIX_FILE + +#include "file_test.hpp" + +#include +#include + +namespace beast { + +class file_posix_test + : public beast::unit_test::suite +{ +public: + void + run() + { + doTestFile(*this); + } +}; + +BEAST_DEFINE_TESTSUITE(file_posix,core,beast); + +} // beast + +#endif diff --git a/test/core/file_stdio.cpp b/test/core/file_stdio.cpp new file mode 100644 index 0000000000..3bb61e242f --- /dev/null +++ b/test/core/file_stdio.cpp @@ -0,0 +1,31 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include "file_test.hpp" + +#include +#include + +namespace beast { + +class file_stdio_test + : public beast::unit_test::suite +{ +public: + void + run() + { + doTestFile(*this); + } +}; + +BEAST_DEFINE_TESTSUITE(file_stdio,core,beast); + +} // beast diff --git a/test/core/file_test.hpp b/test/core/file_test.hpp new file mode 100644 index 0000000000..2249a4c0b0 --- /dev/null +++ b/test/core/file_test.hpp @@ -0,0 +1,122 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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_TEST_CORE_FILE_TEST_HPP +#define BEAST_TEST_CORE_FILE_TEST_HPP + +#include +#include +#include +#include +#include + +namespace beast { + +template +void +doTestFile(beast::unit_test::suite& test) +{ + BOOST_STATIC_ASSERT(is_file::value); + + error_code ec; + auto const temp = boost::filesystem::unique_path(); + + { + { + File f1; + test.BEAST_EXPECT(! f1.is_open()); + f1.open(temp.string().c_str(), file_mode::write, ec); + test.BEAST_EXPECT(! ec); + File f2{std::move(f1)}; + test.BEAST_EXPECT(! f1.is_open()); + test.BEAST_EXPECT(f2.is_open()); + File f3; + f3 = std::move(f2); + test.BEAST_EXPECT(! f2.is_open()); + test.BEAST_EXPECT(f3.is_open()); + f1.close(ec); + test.BEAST_EXPECT(! ec); + auto const temp2 = boost::filesystem::unique_path(); + f3.open(temp2.string().c_str(), file_mode::read, ec); + test.BEAST_EXPECT(ec); + ec.assign(0, ec.category()); + } + boost::filesystem::remove(temp, ec); + test.BEAST_EXPECT(! ec); + } + + File f; + + test.BEAST_EXPECT(! f.is_open()); + + f.size(ec); + test.BEAST_EXPECT(ec == errc::invalid_argument); + ec.assign(0, ec.category()); + + f.pos(ec); + test.BEAST_EXPECT(ec == errc::invalid_argument); + ec.assign(0, ec.category()); + + f.seek(0, ec); + test.BEAST_EXPECT(ec == errc::invalid_argument); + ec.assign(0, ec.category()); + + f.read(nullptr, 0, ec); + test.BEAST_EXPECT(ec == errc::invalid_argument); + ec.assign(0, ec.category()); + + f.write(nullptr, 0, ec); + test.BEAST_EXPECT(ec == errc::invalid_argument); + ec.assign(0, ec.category()); + + f.open(temp.string().c_str(), file_mode::write, ec); + test.BEAST_EXPECT(! ec); + + std::string const s = "Hello, world!"; + f.write(s.data(), s.size(), ec); + test.BEAST_EXPECT(! ec); + + auto size = f.size(ec); + test.BEAST_EXPECT(! ec); + test.BEAST_EXPECT(size == s.size()); + + auto pos = f.pos(ec); + test.BEAST_EXPECT(! ec); + test.BEAST_EXPECT(pos == size); + + f.close(ec); + test.BEAST_EXPECT(! ec); + + f.open(temp.string().c_str(), file_mode::read, ec); + test.BEAST_EXPECT(! ec); + + std::string buf; + buf.resize(s.size()); + f.read(&buf[0], buf.size(), ec); + test.BEAST_EXPECT(! ec); + test.BEAST_EXPECT(buf == s); + + f.seek(1, ec); + test.BEAST_EXPECT(! ec); + buf.resize(3); + f.read(&buf[0], buf.size(), ec); + test.BEAST_EXPECT(! ec); + test.BEAST_EXPECT(buf == "ell"); + + pos = f.pos(ec); + test.BEAST_EXPECT(! ec); + test.BEAST_EXPECT(pos == 4); + + f.close(ec); + test.BEAST_EXPECT(! ec); + boost::filesystem::remove(temp, ec); + test.BEAST_EXPECT(! ec); +} + +} // beast + +#endif diff --git a/test/core/file_win32.cpp b/test/core/file_win32.cpp new file mode 100644 index 0000000000..91530a7084 --- /dev/null +++ b/test/core/file_win32.cpp @@ -0,0 +1,35 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#if BEAST_USE_WIN32_FILE + +#include "file_test.hpp" + +#include +#include + +namespace beast { + +class file_win32_test + : public beast::unit_test::suite +{ +public: + void + run() + { + doTestFile(*this); + } +}; + +BEAST_DEFINE_TESTSUITE(file_win32,core,beast); + +} // beast + +#endif diff --git a/test/core/flat_buffer.cpp b/test/core/flat_buffer.cpp new file mode 100644 index 0000000000..fda65623fe --- /dev/null +++ b/test/core/flat_buffer.cpp @@ -0,0 +1,352 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include "buffer_test.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { + +static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + +class flat_buffer_test : public beast::unit_test::suite +{ +public: + void + testBuffer() + { + using namespace test; + + using a_t = test::test_allocator; + + // Equal == false + using a_neq_t = test::test_allocator; + + // construction + { + { + flat_buffer b; + BEAST_EXPECT(b.capacity() == 0); + } + { + flat_buffer b{500}; + BEAST_EXPECT(b.capacity() == 0); + BEAST_EXPECT(b.max_size() == 500); + } + { + a_neq_t a1; + basic_flat_buffer b{a1}; + BEAST_EXPECT(b.get_allocator() == a1); + a_neq_t a2; + BEAST_EXPECT(b.get_allocator() != a2); + } + { + a_neq_t a; + basic_flat_buffer b{500, a}; + BEAST_EXPECT(b.capacity() == 0); + BEAST_EXPECT(b.max_size() == 500); + } + } + + // move construction + { + { + basic_flat_buffer b1{30}; + BEAST_EXPECT(b1.get_allocator()->nmove == 0); + ostream(b1) << "Hello"; + basic_flat_buffer b2{std::move(b1)}; + BEAST_EXPECT(b2.get_allocator()->nmove == 1); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + BEAST_EXPECT(b1.max_size() == b2.max_size()); + } + { + basic_flat_buffer b1{30}; + ostream(b1) << "Hello"; + a_t a; + basic_flat_buffer b2{std::move(b1), a}; + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + BEAST_EXPECT(b1.max_size() == b2.max_size()); + } + { + basic_flat_buffer b1{30}; + ostream(b1) << "Hello"; + a_neq_t a; + basic_flat_buffer b2{std::move(b1), a}; + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + BEAST_EXPECT(b1.max_size() == b2.max_size()); + } + } + + // copy construction + { + basic_flat_buffer b1; + ostream(b1) << "Hello"; + basic_flat_buffer b2(b1); + BEAST_EXPECT(b1.get_allocator() == b2.get_allocator()); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + basic_flat_buffer b1; + ostream(b1) << "Hello"; + a_neq_t a; + basic_flat_buffer b2(b1, a); + BEAST_EXPECT(b1.get_allocator() != b2.get_allocator()); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + basic_flat_buffer b1; + ostream(b1) << "Hello"; + basic_flat_buffer b2(b1); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + basic_flat_buffer b1; + ostream(b1) << "Hello"; + a_t a; + basic_flat_buffer b2(b1, a); + BEAST_EXPECT(b2.get_allocator() == a); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + + // move assignment + { + { + flat_buffer b1; + ostream(b1) << "Hello"; + flat_buffer b2; + b2 = std::move(b1); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + using na_t = test::test_allocator; + basic_flat_buffer b1; + ostream(b1) << "Hello"; + basic_flat_buffer b2; + b2 = std::move(b1); + BEAST_EXPECT(b1.get_allocator() == b2.get_allocator()); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + using na_t = test::test_allocator; + basic_flat_buffer b1; + ostream(b1) << "Hello"; + basic_flat_buffer b2; + b2 = std::move(b1); + BEAST_EXPECT(b1.get_allocator() != b2.get_allocator()); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + // propagate_on_container_move_assignment : true + using pocma_t = test::test_allocator; + basic_flat_buffer b1; + ostream(b1) << "Hello"; + basic_flat_buffer b2; + b2 = std::move(b1); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + // propagate_on_container_move_assignment : false + using pocma_t = test::test_allocator; + basic_flat_buffer b1; + ostream(b1) << "Hello"; + basic_flat_buffer b2; + b2 = std::move(b1); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + } + + // copy assignment + { + { + flat_buffer b1; + ostream(b1) << "Hello"; + flat_buffer b2; + b2 = b1; + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + basic_flat_buffer b3; + b3 = b2; + BEAST_EXPECT(to_string(b3.data()) == "Hello"); + } + { + // propagate_on_container_copy_assignment : true + using pocca_t = test::test_allocator; + basic_flat_buffer b1; + ostream(b1) << "Hello"; + basic_flat_buffer b2; + b2 = b1; + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + // propagate_on_container_copy_assignment : false + using pocca_t = test::test_allocator; + basic_flat_buffer b1; + ostream(b1) << "Hello"; + basic_flat_buffer b2; + b2 = b1; + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + } + + // operations + { + string_view const s = "Hello, world!"; + flat_buffer b1{64}; + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.max_size() == 64); + BEAST_EXPECT(b1.capacity() == 0); + ostream(b1) << s; + BEAST_EXPECT(to_string(b1.data()) == s); + { + flat_buffer b2{b1}; + BEAST_EXPECT(to_string(b2.data()) == s); + b2.consume(7); + BEAST_EXPECT(to_string(b2.data()) == s.substr(7)); + } + { + flat_buffer b2{64}; + b2 = b1; + BEAST_EXPECT(to_string(b2.data()) == s); + b2.consume(7); + BEAST_EXPECT(to_string(b2.data()) == s.substr(7)); + } + } + + // cause memmove + { + flat_buffer b{20}; + ostream(b) << "12345"; + b.consume(3); + ostream(b) << "67890123"; + BEAST_EXPECT(to_string(b.data()) == "4567890123"); + } + + // read_size + { + flat_buffer b{10}; + BEAST_EXPECT(read_size(b, 512) == 10); + b.prepare(4); + b.commit(4); + BEAST_EXPECT(read_size(b, 512) == 6); + b.consume(2); + BEAST_EXPECT(read_size(b, 512) == 8); + b.prepare(8); + b.commit(8); + BEAST_EXPECT(read_size(b, 512) == 0); + } + + // swap + { + { + basic_flat_buffer b1; + ostream(b1) << "Hello"; + basic_flat_buffer b2; + BEAST_EXPECT(b1.get_allocator() != b2.get_allocator()); + swap(b1, b2); + BEAST_EXPECT(b1.get_allocator() != b2.get_allocator()); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + using na_t = test::test_allocator; + na_t a1; + basic_flat_buffer b1{a1}; + na_t a2; + ostream(b1) << "Hello"; + basic_flat_buffer b2{a2}; + BEAST_EXPECT(b1.get_allocator() == a1); + BEAST_EXPECT(b2.get_allocator() == a2); + swap(b1, b2); + BEAST_EXPECT(b1.get_allocator() == b2.get_allocator()); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + } + + // prepare + { + flat_buffer b{100}; + b.prepare(10); + b.commit(10); + b.prepare(5); + BEAST_EXPECT(b.capacity() >= 5); + try + { + b.prepare(1000); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + + // shrink to fit + { + flat_buffer b; + BEAST_EXPECT(b.capacity() == 0); + b.prepare(50); + BEAST_EXPECT(b.capacity() == 50); + b.commit(50); + BEAST_EXPECT(b.capacity() == 50); + b.prepare(75); + BEAST_EXPECT(b.capacity() >= 125); + b.shrink_to_fit(); + BEAST_EXPECT(b.capacity() == b.size()); + + } + } + + void + run() override + { + testBuffer(); + } +}; + +BEAST_DEFINE_TESTSUITE(flat_buffer,core,beast); + +} // beast diff --git a/test/core/get_lowest_layer.cpp b/test/core/get_lowest_layer.cpp deleted file mode 100644 index 11538a83e4..0000000000 --- a/test/core/get_lowest_layer.cpp +++ /dev/null @@ -1,88 +0,0 @@ -// -// 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) -// - -// Test that header file is self-contained. -#include - -#include -#include - -namespace beast { -namespace detail { - -class get_lowest_layer_test - : public beast::unit_test::suite -{ -public: - struct F1 - { - }; - - struct F2 - { - }; - - template - struct F3 - { - using next_layer_type = - typename std::remove_reference::type; - - using lowest_layer_type = typename - get_lowest_layer::type; - }; - - template - struct F4 - { - using next_layer_type = - typename std::remove_reference::type; - - using lowest_layer_type = typename - get_lowest_layer::type; - }; - - void - run() - { - static_assert(! has_lowest_layer::value, ""); - static_assert(! has_lowest_layer::value, ""); - static_assert(has_lowest_layer>::value, ""); - static_assert(has_lowest_layer>>::value, ""); - - static_assert(std::is_same< - get_lowest_layer::type, F1>::value, ""); - - static_assert(std::is_same< - get_lowest_layer::type, F2>::value, ""); - - static_assert(std::is_same< - get_lowest_layer>::type, F1>::value, ""); - - static_assert(std::is_same< - get_lowest_layer>::type, F2>::value, ""); - - static_assert(std::is_same< - get_lowest_layer>::type, F1>::value, ""); - - static_assert(std::is_same< - get_lowest_layer>::type, F2>::value, ""); - - static_assert(std::is_same< - get_lowest_layer>>::type, F1>::value, ""); - - static_assert(std::is_same< - get_lowest_layer>>::type, F2>::value, ""); - - pass(); - } -}; - -BEAST_DEFINE_TESTSUITE(get_lowest_layer,core,beast); - -} // detail -} // beast diff --git a/test/core/handler_alloc.cpp b/test/core/handler_alloc.cpp index c657d5fb31..64acc0749c 100644 --- a/test/core/handler_alloc.cpp +++ b/test/core/handler_alloc.cpp @@ -9,6 +9,7 @@ #include #include +#include #include namespace beast { @@ -24,9 +25,23 @@ public: } }; + // https://github.com/vinniefalco/Beast/issues/432 + void + testRegression432() + { + handler h; + handler_alloc a{h}; + std::list> v{a}; + v.push_back(1); + v.push_back(2); + v.push_back(3); + } + void run() override { + testRegression432(); + handler h; handler h2; handler_alloc a1{h}; diff --git a/test/core/is_call_possible.cpp b/test/core/is_call_possible.cpp deleted file mode 100644 index 5849793ddc..0000000000 --- a/test/core/is_call_possible.cpp +++ /dev/null @@ -1,56 +0,0 @@ -// -// 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) -// - -// Test that header file is self-contained. -#include - -namespace beast { -namespace detail { -namespace { - -struct is_call_possible_udt1 -{ - void operator()(int) const; -}; - -struct is_call_possible_udt2 -{ - int operator()(int) const; -}; - -struct is_call_possible_udt3 -{ - int operator()(int); -}; - -#ifndef __INTELLISENSE__ -// VFALCO Fails to compile with Intellisense -static_assert(is_call_possible< - is_call_possible_udt1, void(int)>::value, ""); - -static_assert(! is_call_possible< - is_call_possible_udt1, void(void)>::value, ""); - -static_assert(is_call_possible< - is_call_possible_udt2, int(int)>::value, ""); - -static_assert(! is_call_possible< - is_call_possible_udt2, int(void)>::value, ""); - -static_assert(! is_call_possible< - is_call_possible_udt2, void(void)>::value, ""); - -static_assert(is_call_possible< - is_call_possible_udt3, int(int)>::value, ""); - -static_assert(! is_call_possible< - is_call_possible_udt3 const, int(int)>::value, ""); -#endif - -} -} // detail -} // beast diff --git a/test/core/multi_buffer.cpp b/test/core/multi_buffer.cpp new file mode 100644 index 0000000000..f2e3dbf3b3 --- /dev/null +++ b/test/core/multi_buffer.cpp @@ -0,0 +1,592 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include + +#include "buffer_test.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { + +BOOST_STATIC_ASSERT(is_dynamic_buffer::value); + +class multi_buffer_test : public beast::unit_test::suite +{ +public: + template + static + bool + eq(basic_multi_buffer const& mb1, + basic_multi_buffer const& mb2) + { + return test::to_string(mb1.data()) == + test::to_string(mb2.data()); + } + + template + void + expect_size(std::size_t n, ConstBufferSequence const& buffers) + { + BEAST_EXPECT(test::size_pre(buffers) == n); + BEAST_EXPECT(test::size_post(buffers) == n); + BEAST_EXPECT(test::size_rev_pre(buffers) == n); + BEAST_EXPECT(test::size_rev_post(buffers) == n); + } + + template + static + void + self_assign(U& u, V&& v) + { + u = std::forward(v); + } + + void + testMatrix1() + { + using namespace test; + using boost::asio::buffer; + std::string const s = "Hello, world"; + BEAST_EXPECT(s.size() == 12); + for(std::size_t i = 1; i < 12; ++i) { + for(std::size_t x = 1; x < 4; ++x) { + for(std::size_t y = 1; y < 4; ++y) { + std::size_t z = s.size() - (x + y); + { + multi_buffer b; + b.commit(buffer_copy(b.prepare(x), buffer(s.data(), x))); + b.commit(buffer_copy(b.prepare(y), buffer(s.data()+x, y))); + b.commit(buffer_copy(b.prepare(z), buffer(s.data()+x+y, z))); + BEAST_EXPECT(to_string(b.data()) == s); + { + multi_buffer mb2{b}; + BEAST_EXPECT(eq(b, mb2)); + } + { + multi_buffer mb2; + mb2 = b; + BEAST_EXPECT(eq(b, mb2)); + } + { + multi_buffer mb2{std::move(b)}; + BEAST_EXPECT(to_string(mb2.data()) == s); + expect_size(0, b.data()); + b = std::move(mb2); + BEAST_EXPECT(to_string(b.data()) == s); + expect_size(0, mb2.data()); + } + self_assign(b, b); + BEAST_EXPECT(to_string(b.data()) == s); + self_assign(b, std::move(b)); + BEAST_EXPECT(to_string(b.data()) == s); + } + }}} + } + + void + testMatrix2() + { + using namespace test; + using boost::asio::buffer; + using boost::asio::buffer_size; + std::string const s = "Hello, world"; + BEAST_EXPECT(s.size() == 12); + for(std::size_t i = 1; i < 12; ++i) { + for(std::size_t x = 1; x < 4; ++x) { + for(std::size_t y = 1; y < 4; ++y) { + for(std::size_t t = 1; t < 4; ++ t) { + for(std::size_t u = 1; u < 4; ++ u) { + std::size_t z = s.size() - (x + y); + std::size_t v = s.size() - (t + u); + { + multi_buffer b; + { + auto d = b.prepare(z); + BEAST_EXPECT(buffer_size(d) == z); + } + { + auto d = b.prepare(0); + BEAST_EXPECT(buffer_size(d) == 0); + } + { + auto d = b.prepare(y); + BEAST_EXPECT(buffer_size(d) == y); + } + { + auto d = b.prepare(x); + BEAST_EXPECT(buffer_size(d) == x); + b.commit(buffer_copy(d, buffer(s.data(), x))); + } + BEAST_EXPECT(b.size() == x); + BEAST_EXPECT(buffer_size(b.data()) == b.size()); + { + auto d = b.prepare(x); + BEAST_EXPECT(buffer_size(d) == x); + } + { + auto d = b.prepare(0); + BEAST_EXPECT(buffer_size(d) == 0); + } + { + auto d = b.prepare(z); + BEAST_EXPECT(buffer_size(d) == z); + } + { + auto d = b.prepare(y); + BEAST_EXPECT(buffer_size(d) == y); + b.commit(buffer_copy(d, buffer(s.data()+x, y))); + } + b.commit(1); + BEAST_EXPECT(b.size() == x + y); + BEAST_EXPECT(buffer_size(b.data()) == b.size()); + { + auto d = b.prepare(x); + BEAST_EXPECT(buffer_size(d) == x); + } + { + auto d = b.prepare(y); + BEAST_EXPECT(buffer_size(d) == y); + } + { + auto d = b.prepare(0); + BEAST_EXPECT(buffer_size(d) == 0); + } + { + auto d = b.prepare(z); + BEAST_EXPECT(buffer_size(d) == z); + b.commit(buffer_copy(d, buffer(s.data()+x+y, z))); + } + b.commit(2); + BEAST_EXPECT(b.size() == x + y + z); + BEAST_EXPECT(buffer_size(b.data()) == b.size()); + BEAST_EXPECT(to_string(b.data()) == s); + b.consume(t); + { + auto d = b.prepare(0); + BEAST_EXPECT(buffer_size(d) == 0); + } + BEAST_EXPECT(to_string(b.data()) == s.substr(t, std::string::npos)); + b.consume(u); + BEAST_EXPECT(to_string(b.data()) == s.substr(t + u, std::string::npos)); + b.consume(v); + BEAST_EXPECT(to_string(b.data()) == ""); + b.consume(1); + { + auto d = b.prepare(0); + BEAST_EXPECT(buffer_size(d) == 0); + } + } + }}}}} + } + + void + testIterators() + { + using boost::asio::buffer_size; + multi_buffer b; + b.prepare(1); + b.commit(1); + b.prepare(2); + b.commit(2); + expect_size(3, b.data()); + b.prepare(1); + expect_size(3, b.prepare(3)); + b.commit(2); + } + + void + testMembers() + { + using namespace test; + + // compare equal + using equal_t = test::test_allocator; + + // compare not equal + using unequal_t = test::test_allocator; + + // construction + { + { + multi_buffer b; + BEAST_EXPECT(b.capacity() == 0); + } + { + multi_buffer b{500}; + BEAST_EXPECT(b.capacity() == 0); + BEAST_EXPECT(b.max_size() == 500); + } + { + unequal_t a1; + basic_multi_buffer b{a1}; + BEAST_EXPECT(b.get_allocator() == a1); + BEAST_EXPECT(b.get_allocator() != unequal_t{}); + } + } + + // move construction + { + { + basic_multi_buffer b1{30}; + BEAST_EXPECT(b1.get_allocator()->nmove == 0); + ostream(b1) << "Hello"; + basic_multi_buffer b2{std::move(b1)}; + BEAST_EXPECT(b2.get_allocator()->nmove == 1); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + BEAST_EXPECT(b1.max_size() == b2.max_size()); + } + // allocators equal + { + basic_multi_buffer b1{30}; + ostream(b1) << "Hello"; + equal_t a; + basic_multi_buffer b2{std::move(b1), a}; + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + BEAST_EXPECT(b1.max_size() == b2.max_size()); + } + { + // allocators unequal + basic_multi_buffer b1{30}; + ostream(b1) << "Hello"; + unequal_t a; + basic_multi_buffer b2{std::move(b1), a}; + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + BEAST_EXPECT(b1.max_size() == b2.max_size()); + } + } + + // copy construction + { + { + basic_multi_buffer b1; + ostream(b1) << "Hello"; + basic_multi_buffer b2{b1}; + BEAST_EXPECT(b1.get_allocator() == b2.get_allocator()); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + basic_multi_buffer b1; + ostream(b1) << "Hello"; + unequal_t a; + basic_multi_buffer b2(b1, a); + BEAST_EXPECT(b1.get_allocator() != b2.get_allocator()); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + basic_multi_buffer b1; + ostream(b1) << "Hello"; + basic_multi_buffer b2(b1); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + basic_multi_buffer b1; + ostream(b1) << "Hello"; + equal_t a; + basic_multi_buffer b2(b1, a); + BEAST_EXPECT(b2.get_allocator() == a); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + } + + // move assignment + { + { + multi_buffer b1; + ostream(b1) << "Hello"; + multi_buffer b2; + b2 = std::move(b1); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.capacity() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + // propagate_on_container_move_assignment : true + using pocma_t = test::test_allocator; + basic_multi_buffer b1; + ostream(b1) << "Hello"; + basic_multi_buffer b2; + b2 = std::move(b1); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + // propagate_on_container_move_assignment : false + using pocma_t = test::test_allocator; + basic_multi_buffer b1; + ostream(b1) << "Hello"; + basic_multi_buffer b2; + b2 = std::move(b1); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + } + + // copy assignment + { + { + multi_buffer b1; + ostream(b1) << "Hello"; + multi_buffer b2; + b2 = b1; + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + basic_multi_buffer b3; + b3 = b2; + BEAST_EXPECT(to_string(b3.data()) == "Hello"); + } + { + // propagate_on_container_copy_assignment : true + using pocca_t = test::test_allocator; + basic_multi_buffer b1; + ostream(b1) << "Hello"; + basic_multi_buffer b2; + b2 = b1; + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + { + // propagate_on_container_copy_assignment : false + using pocca_t = test::test_allocator; + basic_multi_buffer b1; + ostream(b1) << "Hello"; + basic_multi_buffer b2; + b2 = b1; + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + } + } + + // prepare + { + { + multi_buffer b{100}; + try + { + b.prepare(b.max_size() + 1); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + string_view const s = "Hello, world!"; + multi_buffer b1{64}; + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.max_size() == 64); + BEAST_EXPECT(b1.capacity() == 0); + ostream(b1) << s; + BEAST_EXPECT(to_string(b1.data()) == s); + { + multi_buffer b2{b1}; + BEAST_EXPECT(to_string(b2.data()) == s); + b2.consume(7); + BEAST_EXPECT(to_string(b2.data()) == s.substr(7)); + } + { + multi_buffer b2{64}; + b2 = b1; + BEAST_EXPECT(to_string(b2.data()) == s); + b2.consume(7); + BEAST_EXPECT(to_string(b2.data()) == s.substr(7)); + } + } + { + multi_buffer b; + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 1000); + b.commit(1); + BEAST_EXPECT(b.size() == 1); + BEAST_EXPECT(b.capacity() >= 1000); + b.prepare(1000); + BEAST_EXPECT(b.size() == 1); + BEAST_EXPECT(b.capacity() >= 1000); + b.prepare(1500); + BEAST_EXPECT(b.capacity() >= 1000); + } + { + multi_buffer b; + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 1000); + b.commit(1); + BEAST_EXPECT(b.capacity() >= 1000); + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 1000); + b.prepare(2000); + BEAST_EXPECT(b.capacity() >= 2000); + b.commit(2); + } + { + multi_buffer b; + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 1000); + b.prepare(2000); + BEAST_EXPECT(b.capacity() >= 2000); + b.prepare(4000); + BEAST_EXPECT(b.capacity() >= 4000); + b.prepare(50); + BEAST_EXPECT(b.capacity() >= 50); + } + } + + // commit + { + multi_buffer b; + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 1000); + b.commit(1000); + BEAST_EXPECT(b.size() == 1000); + BEAST_EXPECT(b.capacity() >= 1000); + b.consume(1000); + BEAST_EXPECT(b.size() == 0); + BEAST_EXPECT(b.capacity() == 0); + b.prepare(1000); + b.commit(650); + BEAST_EXPECT(b.size() == 650); + BEAST_EXPECT(b.capacity() >= 1000); + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 1650); + b.commit(100); + BEAST_EXPECT(b.size() == 750); + BEAST_EXPECT(b.capacity() >= 1000); + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 2000); + b.commit(500); + } + + // consume + { + multi_buffer b; + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 1000); + b.commit(1000); + BEAST_EXPECT(b.size() == 1000); + BEAST_EXPECT(b.capacity() >= 1000); + b.prepare(1000); + BEAST_EXPECT(b.capacity() >= 2000); + b.commit(750); + BEAST_EXPECT(b.size() == 1750); + b.consume(500); + BEAST_EXPECT(b.size() == 1250); + b.consume(500); + BEAST_EXPECT(b.size() == 750); + b.prepare(250); + b.consume(750); + BEAST_EXPECT(b.size() == 0); + b.prepare(1000); + b.commit(800); + BEAST_EXPECT(b.size() == 800); + b.prepare(1000); + b.commit(600); + BEAST_EXPECT(b.size() == 1400); + b.consume(1400); + BEAST_EXPECT(b.size() == 0); + } + + // swap + { + { + // propagate_on_container_swap : true + using pocs_t = test::test_allocator; + pocs_t a1, a2; + BEAST_EXPECT(a1 != a2); + basic_multi_buffer b1{a1}; + ostream(b1) << "Hello"; + basic_multi_buffer b2{a2}; + BEAST_EXPECT(b1.get_allocator() == a1); + BEAST_EXPECT(b2.get_allocator() == a2); + swap(b1, b2); + BEAST_EXPECT(b1.get_allocator() == a2); + BEAST_EXPECT(b2.get_allocator() == a1); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + swap(b1, b2); + BEAST_EXPECT(b1.get_allocator() == a1); + BEAST_EXPECT(b2.get_allocator() == a2); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(b2.size() == 0); + } + { + // propagate_on_container_swap : false + using pocs_t = test::test_allocator; + pocs_t a1, a2; + BEAST_EXPECT(a1 == a2); + BEAST_EXPECT(a1.id() != a2.id()); + basic_multi_buffer b1{a1}; + ostream(b1) << "Hello"; + basic_multi_buffer b2{a2}; + BEAST_EXPECT(b1.get_allocator() == a1); + BEAST_EXPECT(b2.get_allocator() == a2); + swap(b1, b2); + BEAST_EXPECT(b1.get_allocator().id() == a1.id()); + BEAST_EXPECT(b2.get_allocator().id() == a2.id()); + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(to_string(b2.data()) == "Hello"); + swap(b1, b2); + BEAST_EXPECT(b1.get_allocator().id() == a1.id()); + BEAST_EXPECT(b2.get_allocator().id() == a2.id()); + BEAST_EXPECT(to_string(b1.data()) == "Hello"); + BEAST_EXPECT(b2.size() == 0); + } + } + + // read_size + { + multi_buffer b{10}; + BEAST_EXPECT(read_size(b, 512) == 10); + b.prepare(4); + b.commit(4); + BEAST_EXPECT(read_size(b, 512) == 6); + b.consume(2); + BEAST_EXPECT(read_size(b, 512) == 8); + b.prepare(8); + b.commit(8); + BEAST_EXPECT(read_size(b, 512) == 0); + } + } + + void + run() override + { + testMatrix1(); + testMatrix2(); + testIterators(); + testMembers(); + } +}; + +BEAST_DEFINE_TESTSUITE(multi_buffer,core,beast); + +} // beast diff --git a/test/core/ostream.cpp b/test/core/ostream.cpp new file mode 100644 index 0000000000..25b805ec31 --- /dev/null +++ b/test/core/ostream.cpp @@ -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) +// + +// Test that header file is self-contained. +#include + +#include +#include +#include +#include + +namespace beast { + +class ostream_test : public beast::unit_test::suite +{ +public: + void + run() override + { + { + multi_buffer b; + auto os = ostream(b); + os << "Hello, world!\n"; + os.flush(); + BEAST_EXPECT(boost::lexical_cast( + buffers(b.data())) == "Hello, world!\n"); + auto os2 = std::move(os); + } + { + auto const s = + "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" + "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" + "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" + "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" + "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" + "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" + "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" + "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" "0123456789abcdef"; + multi_buffer b; + ostream(b) << s; + BEAST_EXPECT(boost::lexical_cast( + buffers(b.data())) == s); + } + } +}; + +BEAST_DEFINE_TESTSUITE(ostream,core,beast); + +} // beast diff --git a/test/core/prepare_buffers.cpp b/test/core/prepare_buffers.cpp index b9d481e3dd..f7db38403b 100644 --- a/test/core/prepare_buffers.cpp +++ b/test/core/prepare_buffers.cpp @@ -8,170 +8,3 @@ // Test that header file is self-contained. #include -#include -#include -#include -#include - -namespace beast { - -class prepare_buffers_test : public beast::unit_test::suite -{ -public: - template - static - std::size_t - bsize1(ConstBufferSequence const& bs) - { - using boost::asio::buffer_size; - std::size_t n = 0; - for(auto it = bs.begin(); it != bs.end(); ++it) - n += buffer_size(*it); - return n; - } - - template - static - std::size_t - bsize2(ConstBufferSequence const& bs) - { - using boost::asio::buffer_size; - std::size_t n = 0; - for(auto it = bs.begin(); it != bs.end(); it++) - n += buffer_size(*it); - return n; - } - - template - static - std::size_t - bsize3(ConstBufferSequence const& bs) - { - using boost::asio::buffer_size; - std::size_t n = 0; - for(auto it = bs.end(); it != bs.begin();) - n += buffer_size(*--it); - return n; - } - - template - static - std::size_t - bsize4(ConstBufferSequence const& bs) - { - using boost::asio::buffer_size; - std::size_t n = 0; - for(auto it = bs.end(); it != bs.begin();) - { - it--; - n += buffer_size(*it); - } - return n; - } - - template - static - std::string - to_string(ConstBufferSequence const& bs) - { - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - std::string s; - s.reserve(buffer_size(bs)); - for(auto const& b : bs) - s.append(buffer_cast(b), - buffer_size(b)); - return s; - } - - template - void testMatrix() - { - using boost::asio::buffer_size; - std::string s = "Hello, world"; - BEAST_EXPECT(s.size() == 12); - for(std::size_t x = 1; x < 4; ++x) { - for(std::size_t y = 1; y < 4; ++y) { - std::size_t z = s.size() - (x + y); - { - std::array bs{{ - BufferType{&s[0], x}, - BufferType{&s[x], y}, - BufferType{&s[x+y], z}}}; - for(std::size_t i = 0; i <= s.size() + 1; ++i) - { - auto pb = prepare_buffers(i, bs); - BEAST_EXPECT(to_string(pb) == s.substr(0, i)); - auto pb2 = pb; - BEAST_EXPECT(to_string(pb2) == to_string(pb)); - pb = prepare_buffers(0, bs); - pb2 = pb; - BEAST_EXPECT(buffer_size(pb2) == 0); - pb2 = prepare_buffers(i, bs); - BEAST_EXPECT(to_string(pb2) == s.substr(0, i)); - } - } - }} - } - - void testNullBuffers() - { - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - using boost::asio::null_buffers; - auto pb0 = prepare_buffers(0, null_buffers{}); - BEAST_EXPECT(buffer_size(pb0) == 0); - auto pb1 = prepare_buffers(1, null_buffers{}); - BEAST_EXPECT(buffer_size(pb1) == 0); - BEAST_EXPECT(buffer_copy(pb0, pb1) == 0); - - using pb_type = decltype(pb0); - consuming_buffers cb(pb0); - BEAST_EXPECT(buffer_size(cb) == 0); - BEAST_EXPECT(buffer_copy(cb, pb1) == 0); - cb.consume(1); - BEAST_EXPECT(buffer_size(cb) == 0); - BEAST_EXPECT(buffer_copy(cb, pb1) == 0); - - auto pbc = prepare_buffers(2, cb); - BEAST_EXPECT(buffer_size(pbc) == 0); - BEAST_EXPECT(buffer_copy(pbc, cb) == 0); - } - - void testIterator() - { - using boost::asio::buffer_size; - using boost::asio::const_buffer; - char b[3]; - std::array bs{{ - const_buffer{&b[0], 1}, - const_buffer{&b[1], 1}, - const_buffer{&b[2], 1}}}; - auto pb = prepare_buffers(2, bs); - BEAST_EXPECT(bsize1(pb) == 2); - BEAST_EXPECT(bsize2(pb) == 2); - BEAST_EXPECT(bsize3(pb) == 2); - BEAST_EXPECT(bsize4(pb) == 2); - std::size_t n = 0; - for(auto it = pb.end(); it != pb.begin(); --it) - { - decltype(pb)::const_iterator it2(std::move(it)); - BEAST_EXPECT(buffer_size(*it2) == 1); - it = std::move(it2); - ++n; - } - BEAST_EXPECT(n == 2); - } - - void run() override - { - testMatrix(); - testMatrix(); - testNullBuffers(); - testIterator(); - } -}; - -BEAST_DEFINE_TESTSUITE(prepare_buffers,core,beast); - -} // beast diff --git a/test/core/read_size.cpp b/test/core/read_size.cpp new file mode 100644 index 0000000000..40a01c0c4e --- /dev/null +++ b/test/core/read_size.cpp @@ -0,0 +1,44 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include + +#include +#include +#include +#include + +#include + +namespace beast { + +class read_size_test : public beast::unit_test::suite +{ +public: + template + void + check() + { + DynamicBuffer buffer; + read_size(buffer, 65536); + pass(); + } + + void + run() override + { + check(); + check(); + check(); + check(); + } +}; + +BEAST_DEFINE_TESTSUITE(read_size,core,beast); + +} // beast diff --git a/test/core/span.cpp b/test/core/span.cpp new file mode 100644 index 0000000000..ec8c7d3e04 --- /dev/null +++ b/test/core/span.cpp @@ -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) +// + +// Test that header file is self-contained. +#include + +#include +#include + +namespace beast { + +class span_test : public beast::unit_test::suite +{ +public: + BOOST_STATIC_ASSERT( + detail::is_contiguous_container< + string_view, char const>::value); + + struct base {}; + struct derived : base {}; + + BOOST_STATIC_ASSERT(detail::is_contiguous_container< + std::vector, char>::value); + + BOOST_STATIC_ASSERT(detail::is_contiguous_container< + std::vector, char const>::value); + + BOOST_STATIC_ASSERT(! detail::is_contiguous_container< + std::vector, base>::value); + + BOOST_STATIC_ASSERT(! detail::is_contiguous_container< + std::vector, base const>::value); + + void + testSpan() + { + span sp{"hello", 5}; + BEAST_EXPECT(sp.size() == 5); + std::string s("world"); + sp = s; + } + + void + run() override + { + testSpan(); + } +}; + +BEAST_DEFINE_TESTSUITE(span,core,beast); + +} // beast diff --git a/test/core/static_streambuf.cpp b/test/core/static_buffer.cpp similarity index 56% rename from test/core/static_streambuf.cpp rename to test/core/static_buffer.cpp index 0add8c4dcf..fb0f4f628e 100644 --- a/test/core/static_streambuf.cpp +++ b/test/core/static_buffer.cpp @@ -6,34 +6,28 @@ // // Test that header file is self-contained. -#include +#include +#include "buffer_test.hpp" + +#include +#include #include -#include #include namespace beast { -class static_streambuf_test : public beast::unit_test::suite +static_assert( + is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + +class static_buffer_test : public beast::unit_test::suite { public: - template - static - std::string - to_string(ConstBufferSequence const& bs) - { - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - std::string s; - s.reserve(buffer_size(bs)); - for(auto const& b : bs) - s.append(buffer_cast(b), - buffer_size(b)); - return s; - } - - void testStaticStreambuf() + void + testStaticBuffer() { + using namespace test; using boost::asio::buffer; using boost::asio::buffer_cast; using boost::asio::buffer_size; @@ -50,7 +44,7 @@ public: std::size_t v = sizeof(buf) - (t + u); { std::memset(buf, 0, sizeof(buf)); - static_streambuf_n ba; + static_buffer_n ba; { auto d = ba.prepare(z); BEAST_EXPECT(buffer_size(d) == z); @@ -128,7 +122,7 @@ public: } try { - ba.prepare(1); + ba.prepare(ba.capacity() - ba.size() + 1); fail(); } catch(...) @@ -139,68 +133,101 @@ public: }}}}}} } - void testIterators() + void + testBuffer() { - static_streambuf_n<2> ba; + using namespace test; + string_view const s = "Hello, world!"; + + // static_buffer { - auto mb = ba.prepare(2); - std::size_t n; - n = 0; - for(auto it = mb.begin(); - it != mb.end(); it++) - ++n; - BEAST_EXPECT(n == 1); - mb = ba.prepare(2); - n = 0; - for(auto it = mb.begin(); - it != mb.end(); ++it) - ++n; - BEAST_EXPECT(n == 1); - mb = ba.prepare(2); - n = 0; - for(auto it = mb.end(); - it != mb.begin(); it--) - ++n; - BEAST_EXPECT(n == 1); - mb = ba.prepare(2); - n = 0; - for(auto it = mb.end(); - it != mb.begin(); --it) - ++n; - BEAST_EXPECT(n == 1); + char buf[64]; + static_buffer b{buf, sizeof(buf)}; + ostream(b) << s; + BEAST_EXPECT(to_string(b.data()) == s); + b.consume(b.size()); + BEAST_EXPECT(to_string(b.data()) == ""); + } + + // static_buffer_n + { + static_buffer_n<64> b1; + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.max_size() == 64); + BEAST_EXPECT(b1.capacity() == 64); + ostream(b1) << s; + BEAST_EXPECT(to_string(b1.data()) == s); + { + static_buffer_n<64> b2{b1}; + BEAST_EXPECT(to_string(b2.data()) == s); + b2.consume(7); + BEAST_EXPECT(to_string(b2.data()) == s.substr(7)); + } + { + static_buffer_n<64> b2; + b2 = b1; + BEAST_EXPECT(to_string(b2.data()) == s); + b2.consume(7); + BEAST_EXPECT(to_string(b2.data()) == s.substr(7)); + } + } + + // cause memmove + { + static_buffer_n<10> b; + write_buffer(b, "12345"); + b.consume(3); + write_buffer(b, "67890123"); + BEAST_EXPECT(to_string(b.data()) == "4567890123"); + try + { + b.prepare(1); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + + // read_size + { + static_buffer_n<10> b; + BEAST_EXPECT(read_size(b, 512) == 10); + b.prepare(4); + b.commit(4); + BEAST_EXPECT(read_size(b, 512) == 6); + b.consume(2); + BEAST_EXPECT(read_size(b, 512) == 8); + b.prepare(8); + b.commit(8); + BEAST_EXPECT(read_size(b, 512) == 0); + } + + // base + { + static_buffer_n<10> b; + [&](static_buffer& base) + { + BEAST_EXPECT(base.max_size() == b.capacity()); + } + (b.base()); + + [&](static_buffer const& base) + { + BEAST_EXPECT(base.max_size() == b.capacity()); + } + (b.base()); } - ba.prepare(2); - ba.commit(1); - std::size_t n; - n = 0; - for(auto it = ba.data().begin(); - it != ba.data().end(); it++) - ++n; - BEAST_EXPECT(n == 1); - n = 0; - for(auto it = ba.data().begin(); - it != ba.data().end(); ++it) - ++n; - BEAST_EXPECT(n == 1); - n = 0; - for(auto it = ba.data().end(); - it != ba.data().begin(); it--) - ++n; - BEAST_EXPECT(n == 1); - n = 0; - for(auto it = ba.data().end(); - it != ba.data().begin(); --it) - ++n; - BEAST_EXPECT(n == 1); } void run() override { - testStaticStreambuf(); - testIterators(); + testBuffer(); + //testStaticBuffer(); } }; -BEAST_DEFINE_TESTSUITE(static_streambuf,core,beast); +BEAST_DEFINE_TESTSUITE(static_buffer,core,beast); } // beastp diff --git a/test/core/static_string.cpp b/test/core/static_string.cpp index cfac29bf82..227b2e721f 100644 --- a/test/core/static_string.cpp +++ b/test/core/static_string.cpp @@ -15,150 +15,1133 @@ namespace beast { class static_string_test : public beast::unit_test::suite { public: - void testMembers() + void + testConstruct() { - using str1 = static_string<1>; - using str2 = static_string<2>; { - str1 s1; - BEAST_EXPECT(s1 == ""); - BEAST_EXPECT(s1.empty()); - BEAST_EXPECT(s1.size() == 0); - BEAST_EXPECT(s1.max_size() == 1); - BEAST_EXPECT(s1.capacity() == 1); - BEAST_EXPECT(s1.begin() == s1.end()); - BEAST_EXPECT(s1.cbegin() == s1.cend()); - BEAST_EXPECT(s1.rbegin() == s1.rend()); - BEAST_EXPECT(s1.crbegin() == s1.crend()); - try - { - BEAST_EXPECT(s1.at(0) == 0); - fail(); - } - catch(std::exception const&) - { - pass(); - } - BEAST_EXPECT(s1.data()[0] == 0); - BEAST_EXPECT(*s1.c_str() == 0); - BEAST_EXPECT(std::distance(s1.begin(), s1.end()) == 0); - BEAST_EXPECT(std::distance(s1.cbegin(), s1.cend()) == 0); - BEAST_EXPECT(std::distance(s1.rbegin(), s1.rend()) == 0); - BEAST_EXPECT(std::distance(s1.crbegin(), s1.crend()) == 0); - BEAST_EXPECT(s1.compare(s1) == 0); - BEAST_EXPECT(s1.to_string() == std::string{}); + static_string<1> s; + BEAST_EXPECT(s.empty()); + BEAST_EXPECT(s.size() == 0); + BEAST_EXPECT(s == ""); + BEAST_EXPECT(*s.end() == 0); } { - str1 const s1; - BEAST_EXPECT(s1 == ""); - BEAST_EXPECT(s1.empty()); - BEAST_EXPECT(s1.size() == 0); - BEAST_EXPECT(s1.max_size() == 1); - BEAST_EXPECT(s1.capacity() == 1); - BEAST_EXPECT(s1.begin() == s1.end()); - BEAST_EXPECT(s1.cbegin() == s1.cend()); - BEAST_EXPECT(s1.rbegin() == s1.rend()); - BEAST_EXPECT(s1.crbegin() == s1.crend()); + static_string<4> s1(3, 'x'); + BEAST_EXPECT(! s1.empty()); + BEAST_EXPECT(s1.size() == 3); + BEAST_EXPECT(s1 == "xxx"); + BEAST_EXPECT(*s1.end() == 0); try { - BEAST_EXPECT(s1.at(0) == 0); - fail(); - } - catch(std::exception const&) - { - pass(); - } - BEAST_EXPECT(s1.data()[0] == 0); - BEAST_EXPECT(*s1.c_str() == 0); - BEAST_EXPECT(std::distance(s1.begin(), s1.end()) == 0); - BEAST_EXPECT(std::distance(s1.cbegin(), s1.cend()) == 0); - BEAST_EXPECT(std::distance(s1.rbegin(), s1.rend()) == 0); - BEAST_EXPECT(std::distance(s1.crbegin(), s1.crend()) == 0); - BEAST_EXPECT(s1.compare(s1) == 0); - BEAST_EXPECT(s1.to_string() == std::string{}); - } - { - str1 s1; - str1 s2("x"); - BEAST_EXPECT(s2 == "x"); - BEAST_EXPECT(s2[0] == 'x'); - BEAST_EXPECT(s2.at(0) == 'x'); - BEAST_EXPECT(s2.front() == 'x'); - BEAST_EXPECT(s2.back() == 'x'); - str1 const s3(s2); - BEAST_EXPECT(s3 == "x"); - BEAST_EXPECT(s3[0] == 'x'); - BEAST_EXPECT(s3.at(0) == 'x'); - BEAST_EXPECT(s3.front() == 'x'); - BEAST_EXPECT(s3.back() == 'x'); - s2 = "y"; - BEAST_EXPECT(s2 == "y"); - BEAST_EXPECT(s3 == "x"); - s1 = s2; - BEAST_EXPECT(s1 == "y"); - s1.clear(); - BEAST_EXPECT(s1.empty()); - BEAST_EXPECT(s1.size() == 0); - } - { - str2 s1("x"); - str1 s2(s1); - BEAST_EXPECT(s2 == "x"); - str1 s3; - s3 = s2; - BEAST_EXPECT(s3 == "x"); - s1 = "xy"; - BEAST_EXPECT(s1.size() == 2); - BEAST_EXPECT(s1[0] == 'x'); - BEAST_EXPECT(s1[1] == 'y'); - BEAST_EXPECT(s1.at(0) == 'x'); - BEAST_EXPECT(s1.at(1) == 'y'); - BEAST_EXPECT(s1.front() == 'x'); - BEAST_EXPECT(s1.back() == 'y'); - auto const s4 = s1; - BEAST_EXPECT(s4[0] == 'x'); - BEAST_EXPECT(s4[1] == 'y'); - BEAST_EXPECT(s4.at(0) == 'x'); - BEAST_EXPECT(s4.at(1) == 'y'); - BEAST_EXPECT(s4.front() == 'x'); - BEAST_EXPECT(s4.back() == 'y'); - try - { - s3 = s1; - fail(); - } - catch(std::exception const&) - { - pass(); - } - try - { - str1 s5(s1); - fail(); - } - catch(std::exception const&) - { - pass(); - } - } - { - str1 s1("x"); - str2 s2; - s2 = s1; - try - { - s1.resize(2); - fail(); + static_string<2> s2(3, 'x'); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<5> s1("12345"); + BEAST_EXPECT(*s1.end() == 0); + static_string<3> s2(s1, 2); + BEAST_EXPECT(s2 == "345"); + BEAST_EXPECT(*s2.end() == 0); + static_string<0> s3(s1, 5); + BEAST_EXPECT(s3.empty()); + BEAST_EXPECT(s3.front() == 0); + BEAST_EXPECT(*s3.end() == 0); + } + { + static_string<5> s1("12345"); + static_string<2> s2(s1, 1, 2); + BEAST_EXPECT(s2 == "23"); + BEAST_EXPECT(*s2.end() == 0); + static_string<0> s3(s1, 5, 1); + BEAST_EXPECT(s3.empty()); + BEAST_EXPECT(s3.front() == 0); + BEAST_EXPECT(*s3.end() == 0); + try + { + static_string<5> s4(s1, 6); + fail("", __FILE__, __LINE__); + } + catch(std::out_of_range const&) + { + pass(); + } + } + { + static_string<5> s1("UVXYZ", 3); + BEAST_EXPECT(s1 == "UVX"); + BEAST_EXPECT(*s1.end() == 0); + static_string<5> s2("X\0""Y\0""Z", 3); + BEAST_EXPECT(std::memcmp( + s2.data(), "X\0""Y", 3) == 0); + BEAST_EXPECT(*s2.end() == 0); + } + { + static_string<5> s1("12345"); + static_string<3> s2( + s1.begin() + 1, s1.begin() + 3); + BEAST_EXPECT(s2 == "23"); + BEAST_EXPECT(*s2.end() == 0); + } + { + static_string<5> s1("12345"); + static_string<5> s2(s1); + BEAST_EXPECT(s2 == "12345"); + BEAST_EXPECT(*s2.end() == 0); + static_string<6> s3(s1); + BEAST_EXPECT(s3 == "12345"); + BEAST_EXPECT(*s3.end() == 0); + try + { + static_string<4> s4(s1); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<3> s1({'1', '2', '3'}); + BEAST_EXPECT(s1 == "123"); + BEAST_EXPECT(*s1.end() == 0); + BEAST_EXPECT( + static_string<0>({}) == static_string<0>()); + try + { + static_string<2> s2({'1', '2', '3'}); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<3> s1( + string_view("123")); + BEAST_EXPECT(s1 == "123"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<2> s2( + string_view("123")); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<5> s1( + std::string("12345"), 2, 2); + BEAST_EXPECT(s1 == "34"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<2> s2( + std::string("12345"), 1, 3); + fail("", __FILE__, __LINE__); } catch(std::length_error const&) { pass(); } } - pass(); } - void testCompare() + void + testAssign() + { + { + static_string<3> s1("123"); + static_string<3> s2; + s2 = s1; + BEAST_EXPECT(s2 == "123"); + BEAST_EXPECT(*s2.end() == 0); + } + { + static_string<3> s1("123"); + static_string<5> s2; + s2 = s1; + BEAST_EXPECT(s2 == "123"); + BEAST_EXPECT(*s2.end() == 0); + try + { + static_string<1> s3; + s3 = s1; + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<3> s1; + s1 = "123"; + BEAST_EXPECT(s1 == "123"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<1> s2; + s2 = "123"; + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<1> s1; + s1 = 'x'; + BEAST_EXPECT(s1 == "x"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<0> s2; + s2 = 'x'; + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<3> s1; + s1 = {'1', '2', '3'}; + BEAST_EXPECT(s1 == "123"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<1> s2; + s2 = {'1', '2', '3'}; + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<3> s1; + s1 = string_view("123"); + BEAST_EXPECT(s1 == "123"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<1> s2; + s2 = string_view("123"); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + + { + static_string<4> s1; + s1.assign(3, 'x'); + BEAST_EXPECT(s1 == "xxx"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<2> s2; + s2.assign(3, 'x'); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<5> s1("12345"); + BEAST_EXPECT(*s1.end() == 0); + static_string<5> s2; + s2.assign(s1); + BEAST_EXPECT(s2 == "12345"); + BEAST_EXPECT(*s2.end() == 0); + } + { + static_string<5> s1("12345"); + BEAST_EXPECT(*s1.end() == 0); + static_string<7> s2; + s2.assign(s1); + BEAST_EXPECT(s2 == "12345"); + BEAST_EXPECT(*s2.end() == 0); + try + { + static_string<3> s3; + s3.assign(s1); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<5> s1("12345"); + static_string<5> s2; + s2.assign(s1, 1); + BEAST_EXPECT(s2 == "2345"); + BEAST_EXPECT(*s2.end() == 0); + s2.assign(s1, 1, 2); + BEAST_EXPECT(s2 == "23"); + BEAST_EXPECT(*s2.end() == 0); + s2.assign(s1, 1, 100); + BEAST_EXPECT(s2 == "2345"); + BEAST_EXPECT(*s2.end() == 0); + try + { + s2.assign(s1, 6); + fail("", __FILE__, __LINE__); + } + catch(std::out_of_range const&) + { + pass(); + } + try + { + static_string<3> s3; + s3.assign(s1, 1); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<5> s1; + s1.assign("12"); + BEAST_EXPECT(s1 == "12"); + BEAST_EXPECT(*s1.end() == 0); + s1.assign("12345"); + BEAST_EXPECT(s1 == "12345"); + BEAST_EXPECT(*s1.end() == 0); + } + { + static_string<5> s1; + s1.assign("12345", 3); + BEAST_EXPECT(s1 == "123"); + BEAST_EXPECT(*s1.end() == 0); + } + { + static_string<5> s1("12345"); + static_string<3> s2; + s2.assign(s1.begin(), s1.begin() + 2); + BEAST_EXPECT(s2 == "12"); + BEAST_EXPECT(*s2.end() == 0); + try + { + s2.assign(s1.begin(), s1.end()); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<5> s1; + s1.assign({'1', '2', '3'}); + BEAST_EXPECT(s1 == "123"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<1> s2; + s2.assign({'1', '2', '3'}); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<5> s1; + s1.assign(string_view("123")); + BEAST_EXPECT(s1 == "123"); + BEAST_EXPECT(*s1.end() == 0); + s1.assign(string_view("12345")); + BEAST_EXPECT(s1 == "12345"); + BEAST_EXPECT(*s1.end() == 0); + try + { + s1.assign(string_view("1234567")); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<5> s1; + s1.assign(std::string("12345"), 2, 2); + BEAST_EXPECT(s1 == "34"); + BEAST_EXPECT(*s1.end() == 0); + s1.assign(std::string("12345"), 3); + BEAST_EXPECT(s1 == "45"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<2> s2; + s2.assign( + std::string("12345"), 1, 3); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + } + + void + testAccess() + { + { + static_string<5> s("12345"); + BEAST_EXPECT(s.at(1) == '2'); + BEAST_EXPECT(s.at(4) == '5'); + try + { + BEAST_EXPECT(s.at(5) == 0); + fail("", __FILE__, __LINE__); + } + catch(std::out_of_range const&) + { + pass(); + } + } + { + static_string<5> const s("12345"); + BEAST_EXPECT(s.at(1) == '2'); + BEAST_EXPECT(s.at(4) == '5'); + try + { + BEAST_EXPECT(s.at(5) == 0); + fail("", __FILE__, __LINE__); + } + catch(std::out_of_range const&) + { + pass(); + } + } + { + static_string<5> s("12345"); + BEAST_EXPECT(s[1] == '2'); + BEAST_EXPECT(s[4] == '5'); + s[1] = '_'; + BEAST_EXPECT(s == "1_345"); + } + { + static_string<5> const s("12345"); + BEAST_EXPECT(s[1] == '2'); + BEAST_EXPECT(s[4] == '5'); + BEAST_EXPECT(s[5] == 0); + } + { + static_string<3> s("123"); + BEAST_EXPECT(s.front() == '1'); + BEAST_EXPECT(s.back() == '3'); + s.front() = '_'; + BEAST_EXPECT(s == "_23"); + s.back() = '_'; + BEAST_EXPECT(s == "_2_"); + } + { + static_string<3> const s("123"); + BEAST_EXPECT(s.front() == '1'); + BEAST_EXPECT(s.back() == '3'); + } + { + static_string<3> s("123"); + BEAST_EXPECT(std::memcmp( + s.data(), "123", 3) == 0); + } + { + static_string<3> const s("123"); + BEAST_EXPECT(std::memcmp( + s.data(), "123", 3) == 0); + } + { + static_string<3> s("123"); + BEAST_EXPECT(std::memcmp( + s.c_str(), "123\0", 4) == 0); + } + { + static_string<3> s("123"); + string_view sv = s; + BEAST_EXPECT(static_string<5>(sv) == "123"); + } + } + + void + testIterators() + { + { + static_string<3> s; + BEAST_EXPECT(std::distance( + s.begin(), s.end()) == 0); + BEAST_EXPECT(std::distance( + s.rbegin(), s.rend()) == 0); + s = "123"; + BEAST_EXPECT(std::distance( + s.begin(), s.end()) == 3); + BEAST_EXPECT(std::distance( + s.rbegin(), s.rend()) == 3); + } + { + static_string<3> const s("123"); + BEAST_EXPECT(std::distance( + s.begin(), s.end()) == 3); + BEAST_EXPECT(std::distance( + s.cbegin(), s.cend()) == 3); + BEAST_EXPECT(std::distance( + s.rbegin(), s.rend()) == 3); + BEAST_EXPECT(std::distance( + s.crbegin(), s.crend()) == 3); + } + } + + void + testCapacity() + { + static_string<3> s; + BEAST_EXPECT(s.empty()); + BEAST_EXPECT(s.size() == 0); + BEAST_EXPECT(s.length() == 0); + BEAST_EXPECT(s.max_size() == 3); + BEAST_EXPECT(s.capacity() == 3); + s = "123"; + BEAST_EXPECT(! s.empty()); + BEAST_EXPECT(s.size() == 3); + BEAST_EXPECT(s.length() == 3); + s.reserve(0); + s.reserve(3); + try + { + s.reserve(4); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + s.shrink_to_fit(); + BEAST_EXPECT(! s.empty()); + BEAST_EXPECT(s.size() == 3); + BEAST_EXPECT(s.length() == 3); + BEAST_EXPECT(*s.end() == 0); + } + + void + testOperations() + { + // + // clear + // + + { + static_string<3> s("123"); + s.clear(); + BEAST_EXPECT(s.empty()); + BEAST_EXPECT(*s.end() == 0); + } + + // + // insert + // + + { + static_string<7> s1("12345"); + s1.insert(2, 2, '_'); + BEAST_EXPECT(s1 == "12__345"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<6> s2("12345"); + s2.insert(2, 2, '_'); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + try + { + static_string<6> s2("12345"); + s2.insert(6, 2, '_'); + fail("", __FILE__, __LINE__); + } + catch(std::out_of_range const&) + { + pass(); + } + } + { + static_string<7> s1("12345"); + s1.insert(2, "__"); + BEAST_EXPECT(s1 == "12__345"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<6> s2("12345"); + s2.insert(2, "__"); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + try + { + static_string<6> s2("12345"); + s2.insert(6, "__"); + fail("", __FILE__, __LINE__); + } + catch(std::out_of_range const&) + { + pass(); + } + } + { + static_string<7> s1("12345"); + s1.insert(2, "TUV", 2); + BEAST_EXPECT(s1 == "12TU345"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<6> s2("12345"); + s2.insert(2, "TUV", 2); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + try + { + static_string<6> s2("12345"); + s2.insert(6, "TUV", 2); + fail("", __FILE__, __LINE__); + } + catch(std::out_of_range const&) + { + pass(); + } + } + { + static_string<7> s1("12345"); + s1.insert(2, static_string<3>("TU")); + BEAST_EXPECT(s1 == "12TU345"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<6> s2("12345"); + s2.insert(2, static_string<3>("TUV")); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + try + { + static_string<6> s2("12345"); + s2.insert(6, static_string<3>("TUV")); + fail("", __FILE__, __LINE__); + } + catch(std::out_of_range const&) + { + pass(); + } + } + { + static_string<7> s1("12345"); + s1.insert(2, static_string<3>("TUV"), 1); + BEAST_EXPECT(s1 == "12UV345"); + BEAST_EXPECT(*s1.end() == 0); + s1 = "12345"; + s1.insert(2, static_string<3>("TUV"), 1, 1); + BEAST_EXPECT(s1 == "12U345"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<6> s2("12345"); + s2.insert(2, static_string<3>("TUV"), 1, 2); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + try + { + static_string<6> s2("12345"); + s2.insert(6, static_string<3>("TUV"), 1, 2); + fail("", __FILE__, __LINE__); + } + catch(std::out_of_range const&) + { + pass(); + } + } + { + static_string<4> s1("123"); + s1.insert(s1.begin() + 1, '_'); + BEAST_EXPECT(s1 == "1_23"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<3> s2("123"); + s2.insert(s2.begin() + 1, '_'); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<4> s1("12"); + s1.insert(s1.begin() + 1, 2, '_'); + BEAST_EXPECT(s1 == "1__2"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<4> s2("123"); + s2.insert(s2.begin() + 1, 2, ' '); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<3> s1("123"); + static_string<5> s2("UV"); + s2.insert(s2.begin() + 1, s1.begin(), s1.end()); + BEAST_EXPECT(s2 == "U123V"); + BEAST_EXPECT(*s2.end() == 0); + try + { + static_string<4> s3("UV"); + s3.insert(s3.begin() + 1, s1.begin(), s1.end()); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<5> s1("123"); + s1.insert(1, string_view("UV")); + BEAST_EXPECT(s1 == "1UV23"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<4> s2("123"); + s2.insert(1, string_view("UV")); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + try + { + static_string<5> s2("123"); + s2.insert(5, string_view("UV")); + fail("", __FILE__, __LINE__); + } + catch(std::out_of_range const&) + { + pass(); + } + } + { + static_string<5> s1("123"); + s1.insert(1, std::string("UV")); + BEAST_EXPECT(s1 == "1UV23"); + BEAST_EXPECT(*s1.end() == 0); + try + { + s1.insert(1, std::string("UV")); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<6> s1("123"); + s1.insert(1, std::string("UVX"), 1); + BEAST_EXPECT(s1 == "1VX23"); + BEAST_EXPECT(*s1.end() == 0); + s1.insert(4, std::string("PQR"), 1, 1); + BEAST_EXPECT(s1 == "1VX2Q3"); + BEAST_EXPECT(*s1.end() == 0); + try + { + s1.insert(4, std::string("PQR"), 1, 1); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + + // + // erase + // + + { + static_string<9> s1("123456789"); + BEAST_EXPECT(s1.erase(1, 1) == "13456789"); + BEAST_EXPECT(s1 == "13456789"); + BEAST_EXPECT(*s1.end() == 0); + BEAST_EXPECT(s1.erase(5) == "13456"); + BEAST_EXPECT(s1 == "13456"); + BEAST_EXPECT(*s1.end() == 0); + try + { + s1.erase(7); + fail("", __FILE__, __LINE__); + } + catch(std::out_of_range const&) + { + pass(); + } + } + { + static_string<9> s1("123456789"); + BEAST_EXPECT(*s1.erase(s1.begin() + 5) == '7'); + BEAST_EXPECT(s1 == "12345789"); + BEAST_EXPECT(*s1.end() == 0); + } + { + static_string<9> s1("123456789"); + BEAST_EXPECT(*s1.erase( + s1.begin() + 5, s1.begin() + 7) == '8'); + BEAST_EXPECT(s1 == "1234589"); + BEAST_EXPECT(*s1.end() == 0); + } + + // + // push_back + // + + { + static_string<3> s1("12"); + s1.push_back('3'); + BEAST_EXPECT(s1 == "123"); + try + { + s1.push_back('4'); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + static_string<0> s2; + try + { + s2.push_back('_'); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + + // + // pop_back + // + + { + static_string<3> s1("123"); + s1.pop_back(); + BEAST_EXPECT(s1 == "12"); + BEAST_EXPECT(*s1.end() == 0); + s1.pop_back(); + BEAST_EXPECT(s1 == "1"); + BEAST_EXPECT(*s1.end() == 0); + s1.pop_back(); + BEAST_EXPECT(s1.empty()); + BEAST_EXPECT(*s1.end() == 0); + } + + // + // append + // + + { + static_string<3> s1("1"); + s1.append(2, '_'); + BEAST_EXPECT(s1 == "1__"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<2> s2("1"); + s2.append(2, '_'); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<2> s1("__"); + static_string<3> s2("1"); + s2.append(s1); + BEAST_EXPECT(s2 == "1__"); + BEAST_EXPECT(*s2.end() == 0); + try + { + static_string<2> s3("1"); + s3.append(s1); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<3> s1("XYZ"); + static_string<4> s2("12"); + s2.append(s1, 1); + BEAST_EXPECT(s2 == "12YZ"); + BEAST_EXPECT(*s2.end() == 0); + static_string<3> s3("12"); + s3.append(s1, 1, 1); + BEAST_EXPECT(s3 == "12Y"); + BEAST_EXPECT(*s3.end() == 0); + try + { + static_string<3> s4("12"); + s4.append(s1, 3); + fail("", __FILE__, __LINE__); + } + catch(std::out_of_range const&) + { + pass(); + } + try + { + static_string<3> s4("12"); + s4.append(s1, 1); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<4> s1("12"); + s1.append("XYZ", 2); + BEAST_EXPECT(s1 == "12XY"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<3> s3("12"); + s3.append("XYZ", 2); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<5> s1("12"); + s1.append("XYZ"); + BEAST_EXPECT(s1 == "12XYZ"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<4> s2("12"); + s2.append("XYZ"); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<3> s1("XYZ"); + static_string<5> s2("12"); + s2.append(s1.begin(), s1.end()); + BEAST_EXPECT(s2 == "12XYZ"); + BEAST_EXPECT(*s2.end() == 0); + try + { + static_string<4> s3("12"); + s3.append(s1.begin(), s1.end()); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<5> s1("123"); + s1.append({'X', 'Y'}); + BEAST_EXPECT(s1 == "123XY"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<4> s2("123"); + s2.append({'X', 'Y'}); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + string_view s1("XYZ"); + static_string<5> s2("12"); + s2.append(s1); + BEAST_EXPECT(s2 == "12XYZ"); + BEAST_EXPECT(*s2.end() == 0); + try + { + static_string<4> s3("12"); + s3.append(s1); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<6> s1("123"); + s1.append(std::string("UVX"), 1); + BEAST_EXPECT(s1 == "123VX"); + BEAST_EXPECT(*s1.end() == 0); + s1.append(std::string("PQR"), 1, 1); + BEAST_EXPECT(s1 == "123VXQ"); + BEAST_EXPECT(*s1.end() == 0); + try + { + static_string<3> s2("123"); + s2.append(std::string("PQR"), 1, 1); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + + // + // operator+= + // + + { + static_string<2> s1("__"); + static_string<3> s2("1"); + s2 += s1; + BEAST_EXPECT(s2 == "1__"); + BEAST_EXPECT(*s2.end() == 0); + try + { + static_string<2> s3("1"); + s3 += s1; + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<3> s1("12"); + s1 += '3'; + BEAST_EXPECT(s1 == "123"); + try + { + s1 += '4'; + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<4> s1("12"); + s1 += "34"; + BEAST_EXPECT(s1 == "1234"); + try + { + s1 += "5"; + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + static_string<4> s1("12"); + s1 += {'3', '4'}; + BEAST_EXPECT(s1 == "1234"); + try + { + s1 += {'5'}; + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + { + string_view s1("34"); + static_string<4> s2("12"); + s2 += s1; + BEAST_EXPECT(s2 == "1234"); + try + { + s2 += s1; + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + } + + void + testCompare() { using str1 = static_string<1>; using str2 = static_string<2>; @@ -251,10 +1234,240 @@ public: } } - void run() override + void + testSwap() { - testMembers(); + { + static_string<3> s1("123"); + static_string<3> s2("XYZ"); + swap(s1, s2); + BEAST_EXPECT(s1 == "XYZ"); + BEAST_EXPECT(*s1.end() == 0); + BEAST_EXPECT(s2 == "123"); + BEAST_EXPECT(*s2.end() == 0); + static_string<3> s3("UV"); + swap(s2, s3); + BEAST_EXPECT(s2 == "UV"); + BEAST_EXPECT(*s2.end() == 0); + BEAST_EXPECT(s3 == "123"); + BEAST_EXPECT(*s3.end() == 0); + } + { + static_string<5> s1("123"); + static_string<7> s2("XYZ"); + swap(s1, s2); + BEAST_EXPECT(s1 == "XYZ"); + BEAST_EXPECT(*s1.end() == 0); + BEAST_EXPECT(s2 == "123"); + BEAST_EXPECT(*s2.end() == 0); + static_string<3> s3("UV"); + swap(s2, s3); + BEAST_EXPECT(s2 == "UV"); + BEAST_EXPECT(*s2.end() == 0); + BEAST_EXPECT(s3 == "123"); + BEAST_EXPECT(*s3.end() == 0); + try + { + static_string<5> s4("12345"); + static_string<3> s5("XYZ"); + swap(s4, s5); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + try + { + static_string<3> s4("XYZ"); + static_string<5> s5("12345"); + swap(s4, s5); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + } + + void + testGeneral() + { + using str1 = static_string<1>; + using str2 = static_string<2>; + { + str1 s1; + BEAST_EXPECT(s1 == ""); + BEAST_EXPECT(s1.empty()); + BEAST_EXPECT(s1.size() == 0); + BEAST_EXPECT(s1.max_size() == 1); + BEAST_EXPECT(s1.capacity() == 1); + BEAST_EXPECT(s1.begin() == s1.end()); + BEAST_EXPECT(s1.cbegin() == s1.cend()); + BEAST_EXPECT(s1.rbegin() == s1.rend()); + BEAST_EXPECT(s1.crbegin() == s1.crend()); + try + { + BEAST_EXPECT(s1.at(0) == 0); + fail(); + } + catch(std::exception const&) + { + pass(); + } + BEAST_EXPECT(s1.data()[0] == 0); + BEAST_EXPECT(*s1.c_str() == 0); + BEAST_EXPECT(std::distance(s1.begin(), s1.end()) == 0); + BEAST_EXPECT(std::distance(s1.cbegin(), s1.cend()) == 0); + BEAST_EXPECT(std::distance(s1.rbegin(), s1.rend()) == 0); + BEAST_EXPECT(std::distance(s1.crbegin(), s1.crend()) == 0); + BEAST_EXPECT(s1.compare(s1) == 0); + } + { + str1 const s1; + BEAST_EXPECT(s1 == ""); + BEAST_EXPECT(s1.empty()); + BEAST_EXPECT(s1.size() == 0); + BEAST_EXPECT(s1.max_size() == 1); + BEAST_EXPECT(s1.capacity() == 1); + BEAST_EXPECT(s1.begin() == s1.end()); + BEAST_EXPECT(s1.cbegin() == s1.cend()); + BEAST_EXPECT(s1.rbegin() == s1.rend()); + BEAST_EXPECT(s1.crbegin() == s1.crend()); + try + { + BEAST_EXPECT(s1.at(0) == 0); + fail(); + } + catch(std::exception const&) + { + pass(); + } + BEAST_EXPECT(s1.data()[0] == 0); + BEAST_EXPECT(*s1.c_str() == 0); + BEAST_EXPECT(std::distance(s1.begin(), s1.end()) == 0); + BEAST_EXPECT(std::distance(s1.cbegin(), s1.cend()) == 0); + BEAST_EXPECT(std::distance(s1.rbegin(), s1.rend()) == 0); + BEAST_EXPECT(std::distance(s1.crbegin(), s1.crend()) == 0); + BEAST_EXPECT(s1.compare(s1) == 0); + } + { + str1 s1; + str1 s2("x"); + BEAST_EXPECT(s2 == "x"); + BEAST_EXPECT(s2[0] == 'x'); + BEAST_EXPECT(s2.at(0) == 'x'); + BEAST_EXPECT(s2.front() == 'x'); + BEAST_EXPECT(s2.back() == 'x'); + str1 const s3(s2); + BEAST_EXPECT(s3 == "x"); + BEAST_EXPECT(s3[0] == 'x'); + BEAST_EXPECT(s3.at(0) == 'x'); + BEAST_EXPECT(s3.front() == 'x'); + BEAST_EXPECT(s3.back() == 'x'); + s2 = "y"; + BEAST_EXPECT(s2 == "y"); + BEAST_EXPECT(s3 == "x"); + s1 = s2; + BEAST_EXPECT(s1 == "y"); + s1.clear(); + BEAST_EXPECT(s1.empty()); + BEAST_EXPECT(s1.size() == 0); + } + { + str2 s1("x"); + str1 s2(s1); + BEAST_EXPECT(s2 == "x"); + str1 s3; + s3 = s2; + BEAST_EXPECT(s3 == "x"); + s1 = "xy"; + BEAST_EXPECT(s1.size() == 2); + BEAST_EXPECT(s1[0] == 'x'); + BEAST_EXPECT(s1[1] == 'y'); + BEAST_EXPECT(s1.at(0) == 'x'); + BEAST_EXPECT(s1.at(1) == 'y'); + BEAST_EXPECT(s1.front() == 'x'); + BEAST_EXPECT(s1.back() == 'y'); + auto const s4 = s1; + BEAST_EXPECT(s4[0] == 'x'); + BEAST_EXPECT(s4[1] == 'y'); + BEAST_EXPECT(s4.at(0) == 'x'); + BEAST_EXPECT(s4.at(1) == 'y'); + BEAST_EXPECT(s4.front() == 'x'); + BEAST_EXPECT(s4.back() == 'y'); + try + { + s3 = s1; + fail(); + } + catch(std::exception const&) + { + pass(); + } + try + { + str1 s5(s1); + fail(); + } + catch(std::exception const&) + { + pass(); + } + } + { + str1 s1("x"); + str2 s2; + s2 = s1; + try + { + s1.resize(2); + fail(); + } + catch(std::length_error const&) + { + pass(); + } + } + pass(); + } + + void + testToStaticString() + { + BEAST_EXPECT(to_static_string(0) == "0"); + BEAST_EXPECT(to_static_string(1) == "1"); + BEAST_EXPECT(to_static_string(0xffff) == "65535"); + BEAST_EXPECT(to_static_string(0x10000) == "65536"); + BEAST_EXPECT(to_static_string(0xffffffff) == "4294967295"); + + BEAST_EXPECT(to_static_string(-1) == "-1"); + BEAST_EXPECT(to_static_string(-65535) == "-65535"); + BEAST_EXPECT(to_static_string(-65536) == "-65536"); + BEAST_EXPECT(to_static_string(-4294967295ll) == "-4294967295"); + + BEAST_EXPECT(to_static_string(0) == "0"); + BEAST_EXPECT(to_static_string(1) == "1"); + BEAST_EXPECT(to_static_string(0xffff) == "65535"); + BEAST_EXPECT(to_static_string(0x10000) == "65536"); + BEAST_EXPECT(to_static_string(0xffffffff) == "4294967295"); + } + + void + run() override + { + testConstruct(); + testAssign(); + testAccess(); + testIterators(); + testCapacity(); + testOperations(); testCompare(); + testSwap(); + + testGeneral(); + testToStaticString(); } }; diff --git a/test/core/stream_concepts.cpp b/test/core/stream_concepts.cpp deleted file mode 100644 index f9d5904096..0000000000 --- a/test/core/stream_concepts.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// -// 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) -// - -// Test that header file is self-contained. -#include -#include - -namespace beast { - -using stream_type = boost::asio::ip::tcp::socket; - -static_assert(has_get_io_service::value, ""); -static_assert(is_AsyncReadStream::value, ""); -static_assert(is_AsyncWriteStream::value, ""); -static_assert(is_AsyncStream::value, ""); -static_assert(is_SyncReadStream::value, ""); -static_assert(is_SyncWriteStream::value, ""); -static_assert(is_SyncStream::value, ""); - -static_assert(! has_get_io_service::value, ""); -static_assert(! is_AsyncReadStream::value, ""); -static_assert(! is_AsyncWriteStream::value, ""); -static_assert(! is_SyncReadStream::value, ""); -static_assert(! is_SyncWriteStream::value, ""); - -} // beast diff --git a/test/core/streambuf.cpp b/test/core/streambuf.cpp deleted file mode 100644 index eb295161f6..0000000000 --- a/test/core/streambuf.cpp +++ /dev/null @@ -1,482 +0,0 @@ -// -// 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) -// - -// Test that header file is self-contained. -#include - -#include "buffer_test.hpp" -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { - -static_assert(is_DynamicBuffer::value, ""); - -struct test_allocator_info -{ - std::size_t ncopy = 0; - std::size_t nmove = 0; - std::size_t nselect = 0; -}; - -template -class test_allocator; - -template -struct test_allocator_base -{ -}; - -template -struct test_allocator_base -{ - static - test_allocator - select_on_container_copy_construction( - test_allocator const& a) - { - return test_allocator{}; - } -}; - -template -class test_allocator : public test_allocator_base< - T, Assign, Move, Swap, Select> -{ - std::size_t id_; - std::shared_ptr info_; - - template - friend class test_allocator; - -public: - using value_type = T; - using propagate_on_container_copy_assignment = - std::integral_constant; - using propagate_on_container_move_assignment = - std::integral_constant; - using propagate_on_container_swap = - std::integral_constant; - - template - struct rebind - { - using other = test_allocator< - U, Assign, Move, Swap, Select>; - }; - - test_allocator() - : id_([] - { - static std::atomic< - std::size_t> sid(0); - return ++sid; - }()) - , info_(std::make_shared()) - { - } - - test_allocator(test_allocator const& u) noexcept - : id_(u.id_) - , info_(u.info_) - { - ++info_->ncopy; - } - - template - test_allocator(test_allocator< - U, Assign, Move, Swap, Select> const& u) noexcept - : id_(u.id_) - , info_(u.info_) - { - ++info_->ncopy; - } - - test_allocator(test_allocator&& t) - : id_(t.id_) - , info_(t.info_) - { - ++info_->nmove; - } - - value_type* - allocate(std::size_t n) - { - return static_cast( - ::operator new (n*sizeof(value_type))); - } - - void - deallocate(value_type* p, std::size_t) noexcept - { - ::operator delete(p); - } - - std::size_t - id() const - { - return id_; - } - - test_allocator_info const* - operator->() const - { - return info_.get(); - } -}; - -class basic_streambuf_test : public beast::unit_test::suite -{ -public: - template - static - bool - eq(basic_streambuf const& sb1, - basic_streambuf const& sb2) - { - return to_string(sb1.data()) == to_string(sb2.data()); - } - - template - void - expect_size(std::size_t n, ConstBufferSequence const& buffers) - { - BEAST_EXPECT(test::size_pre(buffers) == n); - BEAST_EXPECT(test::size_post(buffers) == n); - BEAST_EXPECT(test::size_rev_pre(buffers) == n); - BEAST_EXPECT(test::size_rev_post(buffers) == n); - } - - template - static - void - self_assign(U& u, V&& v) - { - u = std::forward(v); - } - - void testSpecialMembers() - { - using boost::asio::buffer; - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - std::string const s = "Hello, world"; - BEAST_EXPECT(s.size() == 12); - for(std::size_t i = 1; i < 12; ++i) { - for(std::size_t x = 1; x < 4; ++x) { - for(std::size_t y = 1; y < 4; ++y) { - std::size_t z = s.size() - (x + y); - { - streambuf sb(i); - sb.commit(buffer_copy(sb.prepare(x), buffer(s.data(), x))); - sb.commit(buffer_copy(sb.prepare(y), buffer(s.data()+x, y))); - sb.commit(buffer_copy(sb.prepare(z), buffer(s.data()+x+y, z))); - BEAST_EXPECT(to_string(sb.data()) == s); - { - streambuf sb2(sb); - BEAST_EXPECT(eq(sb, sb2)); - } - { - streambuf sb2; - sb2 = sb; - BEAST_EXPECT(eq(sb, sb2)); - } - { - streambuf sb2(std::move(sb)); - BEAST_EXPECT(to_string(sb2.data()) == s); - expect_size(0, sb.data()); - sb = std::move(sb2); - BEAST_EXPECT(to_string(sb.data()) == s); - expect_size(0, sb2.data()); - } - self_assign(sb, sb); - BEAST_EXPECT(to_string(sb.data()) == s); - self_assign(sb, std::move(sb)); - BEAST_EXPECT(to_string(sb.data()) == s); - } - }}} - try - { - streambuf sb0(0); - fail(); - } - catch(std::exception const&) - { - pass(); - } - } - - void testAllocator() - { - // VFALCO This needs work - { - using alloc_type = - test_allocator; - using sb_type = basic_streambuf; - sb_type sb; - BEAST_EXPECT(sb.get_allocator().id() == 1); - } - { - using alloc_type = - test_allocator; - using sb_type = basic_streambuf; - sb_type sb; - BEAST_EXPECT(sb.get_allocator().id() == 2); - sb_type sb2(sb); - BEAST_EXPECT(sb2.get_allocator().id() == 2); - sb_type sb3(sb, alloc_type{}); - } - } - - void - testPrepare() - { - using boost::asio::buffer_size; - { - streambuf sb(2); - BEAST_EXPECT(buffer_size(sb.prepare(5)) == 5); - BEAST_EXPECT(buffer_size(sb.prepare(8)) == 8); - BEAST_EXPECT(buffer_size(sb.prepare(7)) == 7); - } - { - streambuf sb(2); - sb.prepare(2); - BEAST_EXPECT(test::buffer_count(sb.prepare(5)) == 2); - BEAST_EXPECT(test::buffer_count(sb.prepare(8)) == 3); - BEAST_EXPECT(test::buffer_count(sb.prepare(4)) == 2); - } - } - - void testCommit() - { - using boost::asio::buffer_size; - streambuf sb(2); - sb.prepare(2); - sb.prepare(5); - sb.commit(1); - expect_size(1, sb.data()); - } - - void testConsume() - { - using boost::asio::buffer_size; - streambuf sb(1); - expect_size(5, sb.prepare(5)); - sb.commit(3); - expect_size(3, sb.data()); - sb.consume(1); - expect_size(2, sb.data()); - } - - void testMatrix() - { - using boost::asio::buffer; - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - std::string const s = "Hello, world"; - BEAST_EXPECT(s.size() == 12); - for(std::size_t i = 1; i < 12; ++i) { - for(std::size_t x = 1; x < 4; ++x) { - for(std::size_t y = 1; y < 4; ++y) { - for(std::size_t t = 1; t < 4; ++ t) { - for(std::size_t u = 1; u < 4; ++ u) { - std::size_t z = s.size() - (x + y); - std::size_t v = s.size() - (t + u); - { - streambuf sb(i); - { - auto d = sb.prepare(z); - BEAST_EXPECT(buffer_size(d) == z); - } - { - auto d = sb.prepare(0); - BEAST_EXPECT(buffer_size(d) == 0); - } - { - auto d = sb.prepare(y); - BEAST_EXPECT(buffer_size(d) == y); - } - { - auto d = sb.prepare(x); - BEAST_EXPECT(buffer_size(d) == x); - sb.commit(buffer_copy(d, buffer(s.data(), x))); - } - BEAST_EXPECT(sb.size() == x); - BEAST_EXPECT(buffer_size(sb.data()) == sb.size()); - { - auto d = sb.prepare(x); - BEAST_EXPECT(buffer_size(d) == x); - } - { - auto d = sb.prepare(0); - BEAST_EXPECT(buffer_size(d) == 0); - } - { - auto d = sb.prepare(z); - BEAST_EXPECT(buffer_size(d) == z); - } - { - auto d = sb.prepare(y); - BEAST_EXPECT(buffer_size(d) == y); - sb.commit(buffer_copy(d, buffer(s.data()+x, y))); - } - sb.commit(1); - BEAST_EXPECT(sb.size() == x + y); - BEAST_EXPECT(buffer_size(sb.data()) == sb.size()); - { - auto d = sb.prepare(x); - BEAST_EXPECT(buffer_size(d) == x); - } - { - auto d = sb.prepare(y); - BEAST_EXPECT(buffer_size(d) == y); - } - { - auto d = sb.prepare(0); - BEAST_EXPECT(buffer_size(d) == 0); - } - { - auto d = sb.prepare(z); - BEAST_EXPECT(buffer_size(d) == z); - sb.commit(buffer_copy(d, buffer(s.data()+x+y, z))); - } - sb.commit(2); - BEAST_EXPECT(sb.size() == x + y + z); - BEAST_EXPECT(buffer_size(sb.data()) == sb.size()); - BEAST_EXPECT(to_string(sb.data()) == s); - sb.consume(t); - { - auto d = sb.prepare(0); - BEAST_EXPECT(buffer_size(d) == 0); - } - BEAST_EXPECT(to_string(sb.data()) == s.substr(t, std::string::npos)); - sb.consume(u); - BEAST_EXPECT(to_string(sb.data()) == s.substr(t + u, std::string::npos)); - sb.consume(v); - BEAST_EXPECT(to_string(sb.data()) == ""); - sb.consume(1); - { - auto d = sb.prepare(0); - BEAST_EXPECT(buffer_size(d) == 0); - } - } - }}}}} - } - - void testIterators() - { - using boost::asio::buffer_size; - streambuf sb(1); - sb.prepare(1); - sb.commit(1); - sb.prepare(2); - sb.commit(2); - expect_size(3, sb.data()); - sb.prepare(1); - expect_size(3, sb.prepare(3)); - sb.commit(2); - BEAST_EXPECT(test::buffer_count(sb.data()) == 4); - } - - void testOutputStream() - { - streambuf sb; - sb << "x"; - BEAST_EXPECT(to_string(sb.data()) == "x"); - } - - void testCapacity() - { - using boost::asio::buffer_size; - { - streambuf sb{10}; - BEAST_EXPECT(sb.alloc_size() == 10); - BEAST_EXPECT(read_size_helper(sb, 1) == 1); - BEAST_EXPECT(read_size_helper(sb, 10) == 10); - BEAST_EXPECT(read_size_helper(sb, 20) == 20); - BEAST_EXPECT(read_size_helper(sb, 1000) == 512); - sb.prepare(3); - sb.commit(3); - BEAST_EXPECT(read_size_helper(sb, 10) == 7); - BEAST_EXPECT(read_size_helper(sb, 1000) == 7); - } - { - streambuf sb(1000); - BEAST_EXPECT(sb.alloc_size() == 1000); - BEAST_EXPECT(read_size_helper(sb, 1) == 1); - BEAST_EXPECT(read_size_helper(sb, 1000) == 1000); - BEAST_EXPECT(read_size_helper(sb, 2000) == 1000); - sb.prepare(3); - BEAST_EXPECT(read_size_helper(sb, 1) == 1); - BEAST_EXPECT(read_size_helper(sb, 1000) == 1000); - BEAST_EXPECT(read_size_helper(sb, 2000) == 1000); - sb.commit(3); - BEAST_EXPECT(read_size_helper(sb, 1) == 1); - BEAST_EXPECT(read_size_helper(sb, 1000) == 997); - BEAST_EXPECT(read_size_helper(sb, 2000) == 997); - sb.consume(2); - BEAST_EXPECT(read_size_helper(sb, 1) == 1); - BEAST_EXPECT(read_size_helper(sb, 1000) == 997); - BEAST_EXPECT(read_size_helper(sb, 2000) == 997); - } - { - streambuf sb{2}; - BEAST_EXPECT(sb.alloc_size() == 2); - BEAST_EXPECT(test::buffer_count(sb.prepare(2)) == 1); - BEAST_EXPECT(test::buffer_count(sb.prepare(3)) == 2); - BEAST_EXPECT(buffer_size(sb.prepare(5)) == 5); - BEAST_EXPECT(read_size_helper(sb, 10) == 6); - } - { - auto avail = - [](streambuf const& sb) - { - return sb.capacity() - sb.size(); - }; - streambuf sb{100}; - BEAST_EXPECT(sb.alloc_size() == 100); - BEAST_EXPECT(avail(sb) == 0); - sb.prepare(100); - BEAST_EXPECT(avail(sb) == 100); - sb.commit(100); - BEAST_EXPECT(avail(sb) == 0); - sb.consume(100); - BEAST_EXPECT(avail(sb) == 0); - sb.alloc_size(200); - BEAST_EXPECT(sb.alloc_size() == 200); - sb.prepare(1); - BEAST_EXPECT(avail(sb) == 200); - } - } - - void run() override - { - testSpecialMembers(); - testAllocator(); - testPrepare(); - testCommit(); - testConsume(); - testMatrix(); - testIterators(); - testOutputStream(); - testCapacity(); - } -}; - -BEAST_DEFINE_TESTSUITE(basic_streambuf,core,beast); - -} // beast diff --git a/test/http/concepts.cpp b/test/core/string.cpp similarity index 88% rename from test/http/concepts.cpp rename to test/core/string.cpp index 9bc87107c0..c8afa23c86 100644 --- a/test/http/concepts.cpp +++ b/test/core/string.cpp @@ -6,4 +6,4 @@ // // Test that header file is self-contained. -#include +#include diff --git a/test/core/string_param.cpp b/test/core/string_param.cpp new file mode 100644 index 0000000000..4ccfc9a037 --- /dev/null +++ b/test/core/string_param.cpp @@ -0,0 +1,79 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include + +#include +#include + +namespace beast { + +class string_param_test : public unit_test::suite +{ +public: + struct nop {}; + + void + check(string_param const& v, string_view s) + { + BEAST_EXPECT(static_cast(v) == s); + } + + class repeater + { + std::size_t n_; + + public: + explicit + repeater(std::size_t n) + : n_(n) + { + } + + friend + std::ostream& + operator<<(std::ostream& os, repeater const& v) + { + return os << std::string(v.n_, '*'); + } + }; + + void + testConversion() + { + // Make sure things convert correctly + check(std::string("hello"), "hello"); + check("xyz", "xyz"); + check(1, "1"); + check(12, "12"); + check(123, "123"); + check(1234, "1234"); + check(12345, "12345"); + check({"a", "b"}, "ab"); + check({1, 2, 3}, "123"); + } + + void + testStaticOstream() + { + // exercise static_ostream for coverage + std::string s(500, '*'); + check(repeater{500}, s); + } + + void + run() override + { + testConversion(); + testStaticOstream(); + } +}; + +BEAST_DEFINE_TESTSUITE(string_param,core,beast); + +} // beast diff --git a/test/core/to_string.cpp b/test/core/to_string.cpp deleted file mode 100644 index e449dd33aa..0000000000 --- a/test/core/to_string.cpp +++ /dev/null @@ -1,28 +0,0 @@ -// -// 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) -// - -// Test that header file is self-contained. -#include - -#include -#include - -namespace beast { - -class to_string_test : public beast::unit_test::suite -{ -public: - void run() - { - BEAST_EXPECT(to_string(boost::asio::const_buffers_1("x", 1)) == "x"); - } -}; - -BEAST_DEFINE_TESTSUITE(to_string,core,beast); - -} // beast - diff --git a/test/core/type_traits.cpp b/test/core/type_traits.cpp new file mode 100644 index 0000000000..4706155bd3 --- /dev/null +++ b/test/core/type_traits.cpp @@ -0,0 +1,155 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include + +#include +#include +#include + +namespace beast { + +namespace detail { + +namespace { + +// +// is_invocable +// + +struct is_invocable_udt1 +{ + void operator()(int) const; +}; + +struct is_invocable_udt2 +{ + int operator()(int) const; +}; + +struct is_invocable_udt3 +{ + int operator()(int); +}; + +#ifndef __INTELLISENSE__ +// VFALCO Fails to compile with Intellisense +BOOST_STATIC_ASSERT(is_invocable::value); +BOOST_STATIC_ASSERT(is_invocable::value); +BOOST_STATIC_ASSERT(is_invocable::value); +BOOST_STATIC_ASSERT(! is_invocable::value); +BOOST_STATIC_ASSERT(! is_invocable::value); +BOOST_STATIC_ASSERT(! is_invocable::value); +BOOST_STATIC_ASSERT(! is_invocable::value); +#endif + +// +// get_lowest_layer +// + +struct F1 {}; +struct F2 {}; + +template +struct F3 +{ + using next_layer_type = + typename std::remove_reference::type; + + using lowest_layer_type = typename + get_lowest_layer::type; +}; + +template +struct F4 +{ + using next_layer_type = + typename std::remove_reference::type; + + using lowest_layer_type = typename + get_lowest_layer::type; +}; + +BOOST_STATIC_ASSERT(std::is_same::type, F1>::value); +BOOST_STATIC_ASSERT(std::is_same::type, F2>::value); +BOOST_STATIC_ASSERT(std::is_same>::type, F1>::value); +BOOST_STATIC_ASSERT(std::is_same>::type, F2>::value); +BOOST_STATIC_ASSERT(std::is_same>::type, F1>::value); +BOOST_STATIC_ASSERT(std::is_same>::type, F2>::value); +BOOST_STATIC_ASSERT(std::is_same>>::type, F1>::value); +BOOST_STATIC_ASSERT(std::is_same>>::type, F2>::value); + +} // (anonymous) + +} // detail + +// +// buffer concepts +// + +namespace { + +struct T {}; + +BOOST_STATIC_ASSERT(is_const_buffer_sequence::value); +BOOST_STATIC_ASSERT(! is_const_buffer_sequence::value); + +BOOST_STATIC_ASSERT(is_mutable_buffer_sequence::value); +BOOST_STATIC_ASSERT(! is_mutable_buffer_sequence::value); + +BOOST_STATIC_ASSERT(is_dynamic_buffer::value); + +} // (anonymous) + +// +// handler concepts +// + +namespace { + +struct H +{ + void operator()(int); +}; + +} // anonymous + +BOOST_STATIC_ASSERT(is_completion_handler::value); +BOOST_STATIC_ASSERT(! is_completion_handler::value); + +// +// stream concepts +// + +namespace { + +using stream_type = boost::asio::ip::tcp::socket; + +struct not_a_stream +{ + void + get_io_service(); +}; + +BOOST_STATIC_ASSERT(has_get_io_service::value); +BOOST_STATIC_ASSERT(is_async_read_stream::value); +BOOST_STATIC_ASSERT(is_async_write_stream::value); +BOOST_STATIC_ASSERT(is_async_stream::value); +BOOST_STATIC_ASSERT(is_sync_read_stream::value); +BOOST_STATIC_ASSERT(is_sync_write_stream::value); +BOOST_STATIC_ASSERT(is_sync_stream::value); + +BOOST_STATIC_ASSERT(! has_get_io_service::value); +BOOST_STATIC_ASSERT(! is_async_read_stream::value); +BOOST_STATIC_ASSERT(! is_async_write_stream::value); +BOOST_STATIC_ASSERT(! is_sync_read_stream::value); +BOOST_STATIC_ASSERT(! is_sync_write_stream::value); + +} // (anonymous) + +} // beast diff --git a/test/core/write_dynabuf.cpp b/test/core/write_dynabuf.cpp deleted file mode 100644 index 36a65a6c69..0000000000 --- a/test/core/write_dynabuf.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// -// 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) -// - -// Test that header file is self-contained. -#include - -#include -#include - -namespace beast { - -class write_dynabuf_test : public beast::unit_test::suite -{ -public: - void run() override - { - streambuf sb; - std::string s; - write(sb, boost::asio::const_buffer{"", 0}); - write(sb, boost::asio::mutable_buffer{nullptr, 0}); - write(sb, boost::asio::null_buffers{}); - write(sb, boost::asio::const_buffers_1{"", 0}); - write(sb, boost::asio::mutable_buffers_1{nullptr, 0}); - write(sb, s); - write(sb, 23); - pass(); - } -}; - -BEAST_DEFINE_TESTSUITE(write_dynabuf,core,beast); - -} // beast diff --git a/test/exemplars.cpp b/test/exemplars.cpp new file mode 100644 index 0000000000..9a8256a470 --- /dev/null +++ b/test/exemplars.cpp @@ -0,0 +1,345 @@ +// +// 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 +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +class BodyReader; +class BodyWriter; + +//[concept_Body + +struct Body +{ + // The type of message::body when used + struct value_type; + + /// The algorithm used for extracting buffers + using reader = BodyReader; + + /// The algorithm used for inserting buffers + using writer = BodyWriter; + + /// Returns the body's payload size + static + std::uint64_t + size(value_type const& v); +}; + +static_assert(is_body::value, ""); + +//] + +struct Body_BodyReader { + struct value_type{}; +//[concept_BodyReader + +struct BodyReader +{ +public: + /// The type of buffer returned by `get`. + using const_buffers_type = boost::asio::const_buffers_1; + + /** Construct the reader. + + @param msg The message whose body is to be retrieved. + + @param ec Set to the error, if any occurred. + */ + template + explicit + BodyReader(message const& msg); + + /** Initialize the reader + + This is called after construction and before the first + call to `get`. The message is valid and complete upon + entry. + + @param ec Set to the error, if any occurred. + */ + void + init(error_code& ec) + { + // The specification requires this to indicate "no error" + ec.assign(0, ec.category()); + } + + /** Returns the next buffer in the body. + + @li If the return value is `boost::none` (unseated optional) and + `ec` does not contain an error, this indicates the end of the + body, no more buffers are present. + + @li If the optional contains a value, the first element of the + pair represents a @b ConstBufferSequence containing one or + more octets of the body data. The second element indicates + whether or not there are additional octets of body data. + A value of `true` means there is more data, and that the + implementation will perform a subsequent call to `get`. + A value of `false` means there is no more body data. + + @li If `ec` contains an error code, the return value is ignored. + + @param ec Set to the error, if any occurred. + */ + boost::optional> + get(error_code& ec) + { + // The specification requires this to indicate "no error" + ec.assign(0, ec.category()); + + return boost::none; // for exposition only + } +}; + +//] + using reader = BodyReader; +}; + +static_assert(is_body_reader::value, ""); + +struct Body_BodyWriter { + struct value_type{}; +//[concept_BodyWriter + +struct BodyWriter +{ + /** Construct the writer. + + @param msg The message whose body is to be stored. + */ + template + explicit + BodyWriter(message& msg); + + /** Initialize the writer + + This is called after construction and before the first + call to `put`. The message is valid and complete upon + entry. + + @param ec Set to the error, if any occurred. + */ + void + init( + boost::optional const& content_length, + error_code& ec) + { + boost::ignore_unused(content_length); + + // The specification requires this to indicate "no error" + ec.assign(0, ec.category()); + } + + /** Store buffers. + + This is called zero or more times with parsed body octets. + + @param buffers The constant buffer sequence to store. + + @param ec Set to the error, if any occurred. + + @return The number of bytes transferred from the input buffers. + */ + template + std::size_t + put(ConstBufferSequence const& buffers, error_code& ec) + { + // The specification requires this to indicate "no error" + ec = {}; + + return boost::asio::buffer_size(buffers); + } + + /** Called when the body is complete. + + @param ec Set to the error, if any occurred. + */ + void + finish(error_code& ec) + { + // The specification requires this to indicate "no error" + ec = {}; + } +}; + +//] + using writer = BodyWriter; +}; + +static_assert(is_body_writer::value, ""); + +//[concept_Fields + +class Fields +{ +public: + struct reader; + +protected: + /** Returns the request-method string. + + @note Only called for requests. + */ + string_view + get_method_impl() const; + + /** Returns the request-target string. + + @note Only called for requests. + */ + string_view + get_target_impl() const; + + /** Returns the response reason-phrase string. + + @note Only called for responses. + */ + string_view + get_reason_impl() const; + + /** Returns the chunked Transfer-Encoding setting + */ + bool + get_chunked_impl() const; + + /** Returns the keep-alive setting + */ + bool + get_keep_alive_impl(unsigned version) const; + + /** Set or clear the method string. + + @note Only called for requests. + */ + void + set_method_impl(string_view s); + + /** Set or clear the target string. + + @note Only called for requests. + */ + void + set_target_impl(string_view s); + + /** Set or clear the reason string. + + @note Only called for responses. + */ + void + set_reason_impl(string_view s); + + /** Sets or clears the chunked Transfer-Encoding value + */ + void + set_chunked_impl(bool value); + + /** Sets or clears the Content-Length field + */ + void + set_content_length_impl(boost::optional); + + /** Adjusts the Connection field + */ + void + set_keep_alive_impl(unsigned version, bool keep_alive); +}; + +static_assert(is_fields::value, + "Fields requirements not met"); + +//] + +struct Fields_FieldsReader { + using F = Fields_FieldsReader; +//[concept_FieldsReader + +struct FieldsReader +{ + // The type of buffers returned by `get` + struct const_buffers_type; + + // Constructor for requests + FieldsReader(F const& f, unsigned version, verb method); + + // Constructor for responses + FieldsReader(F const& f, unsigned version, unsigned status); + + // Returns `true` if keep-alive is indicated + bool + keep_alive(); + + // Returns the serialized header buffers + const_buffers_type + get(); +}; + +//] +}; + +//[concept_File + +struct File +{ + /** Default constructor + + There is no open file initially. + */ + File(); + + /** Destructor + + If the file is open it is first closed. + */ + ~File(); + + /// Returns `true` if the file is open + bool + is_open() const; + + /// Close the file if open + void + close(error_code& ec); + + /// Open a file at the given path with the specified mode + void + open(char const* path, file_mode mode, error_code& ec); + + /// Return the size of the open file + std::uint64_t + size(error_code& ec) const; + + /// Return the current position in the open file + std::uint64_t + pos(error_code& ec) const; + + /// Adjust the current position in the open file + void + seek(std::uint64_t offset, error_code& ec); + + /// Read from the open file + std::size_t + read(void* buffer, std::size_t n, error_code& ec) const; + + /// Write to the open file + std::size_t + write(void const* buffer, std::size_t n, error_code& ec); +}; + +//] + +} // http +} // beast diff --git a/test/http/CMakeLists.txt b/test/http/CMakeLists.txt index 5505e37dc1..a609f6b156 100644 --- a/test/http/CMakeLists.txt +++ b/test/http/CMakeLists.txt @@ -1,52 +1,47 @@ # Part of Beast +GroupSources(example example) GroupSources(extras/beast extras) GroupSources(include/beast beast) + GroupSources(test/http "/") add_executable (http-tests ${BEAST_INCLUDES} + ${EXAMPLE_INCLUDES} ${EXTRAS_INCLUDES} message_fuzz.hpp - fail_parser.hpp + test_parser.hpp ../../extras/beast/unit_test/main.cpp - basic_dynabuf_body.cpp - basic_fields.cpp - basic_parser_v1.cpp - concepts.cpp + basic_parser.cpp + buffer_body.cpp + doc_examples.cpp + doc_snippets.cpp + dynamic_body.cpp empty_body.cpp + error.cpp + field.cpp fields.cpp - header_parser_v1.cpp + file_body.cpp message.cpp - parse.cpp - parse_error.cpp - parser_v1.cpp + parser.cpp read.cpp - reason.cpp rfc7230.cpp - streambuf_body.cpp + serializer.cpp + span_body.cpp + status.cpp string_body.cpp + type_traits.cpp + vector_body.cpp + verb.cpp write.cpp - chunk_encode.cpp ) -if (NOT WIN32) - target_link_libraries(http-tests ${Boost_LIBRARIES} Threads::Threads) -else() - target_link_libraries(http-tests ${Boost_LIBRARIES}) -endif() - -add_executable (bench-tests - ${BEAST_INCLUDES} - ${EXTRAS_INCLUDES} - nodejs_parser.hpp - ../../extras/beast/unit_test/main.cpp - nodejs_parser.cpp - parser_bench.cpp -) - -if (NOT WIN32) - target_link_libraries(bench-tests ${Boost_LIBRARIES} Threads::Threads) -else() - target_link_libraries(bench-tests ${Boost_LIBRARIES}) -endif() +target_link_libraries(http-tests + Beast + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_COROUTINE_LIBRARY} + ${Boost_THREAD_LIBRARY} + ${Boost_CONTEXT_LIBRARY} + ) diff --git a/test/http/Jamfile b/test/http/Jamfile new file mode 100644 index 0000000000..aca163002c --- /dev/null +++ b/test/http/Jamfile @@ -0,0 +1,31 @@ +# +# 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) +# + +unit-test http-tests : + ../../extras/beast/unit_test/main.cpp + basic_parser.cpp + buffer_body.cpp + doc_examples.cpp + doc_snippets.cpp + dynamic_body.cpp + error.cpp + field.cpp + fields.cpp + file_body.cpp + message.cpp + parser.cpp + read.cpp + rfc7230.cpp + serializer.cpp + span_body.cpp + status.cpp + string_body.cpp + type_traits.cpp + vector_body.cpp + verb.cpp + write.cpp + ; diff --git a/test/http/basic_fields.cpp b/test/http/basic_fields.cpp deleted file mode 100644 index 9948bd63a5..0000000000 --- a/test/http/basic_fields.cpp +++ /dev/null @@ -1,96 +0,0 @@ -// -// 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) -// - -// Test that header file is self-contained. -#include - -#include -#include - -namespace beast { -namespace http { - -class basic_fields_test : public beast::unit_test::suite -{ -public: - template - using bha = basic_fields; - - using bh = basic_fields>; - - template - static - void - fill(std::size_t n, basic_fields& h) - { - for(std::size_t i = 1; i<= n; ++i) - h.insert(boost::lexical_cast(i), i); - } - - template - static - void - self_assign(U& u, V&& v) - { - u = std::forward(v); - } - - void testHeaders() - { - bh h1; - BEAST_EXPECT(h1.empty()); - fill(1, h1); - BEAST_EXPECT(h1.size() == 1); - bh h2; - h2 = h1; - BEAST_EXPECT(h2.size() == 1); - h2.insert("2", "2"); - BEAST_EXPECT(std::distance(h2.begin(), h2.end()) == 2); - h1 = std::move(h2); - BEAST_EXPECT(h1.size() == 2); - BEAST_EXPECT(h2.size() == 0); - bh h3(std::move(h1)); - BEAST_EXPECT(h3.size() == 2); - BEAST_EXPECT(h1.size() == 0); - self_assign(h3, std::move(h3)); - BEAST_EXPECT(h3.size() == 2); - BEAST_EXPECT(h2.erase("Not-Present") == 0); - } - - void testRFC2616() - { - bh h; - h.insert("a", "w"); - h.insert("a", "x"); - h.insert("aa", "y"); - h.insert("b", "z"); - BEAST_EXPECT(h.count("a") == 2); - } - - void testErase() - { - bh h; - h.insert("a", "w"); - h.insert("a", "x"); - h.insert("aa", "y"); - h.insert("b", "z"); - BEAST_EXPECT(h.size() == 4); - h.erase("a"); - BEAST_EXPECT(h.size() == 2); - } - - void run() override - { - testHeaders(); - testRFC2616(); - } -}; - -BEAST_DEFINE_TESTSUITE(basic_fields,http,beast); - -} // http -} // beast diff --git a/test/http/basic_parser.cpp b/test/http/basic_parser.cpp new file mode 100644 index 0000000000..6303fa7f28 --- /dev/null +++ b/test/http/basic_parser.cpp @@ -0,0 +1,1164 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include + +#include "test_parser.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +class basic_parser_test : public beast::unit_test::suite +{ +public: + enum parse_flag + { + chunked = 1, + connection_keep_alive = 2, + connection_close = 4, + connection_upgrade = 8, + upgrade = 16, + }; + + class expect_version + { + suite& s_; + int version_; + + public: + expect_version(suite& s, int version) + : s_(s) + , version_(version) + { + } + + template + void + operator()(Parser const& p) const + { + s_.BEAST_EXPECT(p.version == version_); + } + }; + + class expect_status + { + suite& s_; + int status_; + + public: + expect_status(suite& s, int status) + : s_(s) + , status_(status) + { + } + + template + void + operator()(Parser const& p) const + { + s_.BEAST_EXPECT(p.status == status_); + } + }; + + class expect_flags + { + suite& s_; + unsigned flags_; + + public: + expect_flags(suite& s, unsigned flags) + : s_(s) + , flags_(flags) + { + } + + template + void + operator()(Parser const& p) const + { + if(flags_ & parse_flag::chunked) + s_.BEAST_EXPECT(p.is_chunked()); + if(flags_ & parse_flag::connection_keep_alive) + s_.BEAST_EXPECT(p.is_keep_alive()); + if(flags_ & parse_flag::connection_close) + s_.BEAST_EXPECT(! p.is_keep_alive()); + if(flags_ & parse_flag::upgrade) + s_.BEAST_EXPECT(! p.is_upgrade()); + } + }; + + class expect_keepalive + { + suite& s_; + bool v_; + + public: + expect_keepalive(suite& s, bool v) + : s_(s) + , v_(v) + { + } + + template + void + operator()(Parser const& p) const + { + s_.BEAST_EXPECT(p.is_keep_alive() == v_); + } + }; + + class expect_body + { + suite& s_; + std::string const& body_; + + public: + expect_body(expect_body&&) = default; + + expect_body(suite& s, std::string const& v) + : s_(s) + , body_(v) + { + } + + template + void + operator()(Parser const& p) const + { + s_.BEAST_EXPECT(p.body == body_); + } + }; + + //-------------------------------------------------------------------------- + + template + typename std::enable_if< + is_const_buffer_sequence::value>::type + parsegrind(ConstBufferSequence const& buffers, + Test const& test, bool skip = false) + { + auto const size = boost::asio::buffer_size(buffers); + for(std::size_t i = 1; i < size - 1; ++i) + { + Parser p; + p.eager(true); + p.skip(skip); + error_code ec; + consuming_buffers cb{buffers}; + auto n = p.put(buffer_prefix(i, cb), ec); + if(! BEAST_EXPECTS(! ec || + ec == error::need_more, ec.message())) + continue; + if(! BEAST_EXPECT(! p.is_done())) + continue; + cb.consume(n); + n = p.put(cb, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + continue; + if(! BEAST_EXPECT(n == boost::asio::buffer_size(cb))) + continue; + if(p.need_eof()) + { + p.put_eof(ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + continue; + } + if(! BEAST_EXPECT(p.is_done())) + continue; + test(p); + } + for(std::size_t i = 1; i < size - 1; ++i) + { + Parser p; + p.eager(true); + error_code ec; + consuming_buffers cb{buffers}; + cb.consume(i); + auto n = p.put(buffer_cat( + buffer_prefix(i, buffers), cb), ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + continue; + if(! BEAST_EXPECT(n == size)) + continue; + if(p.need_eof()) + { + p.put_eof(ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + continue; + } + test(p); + } + } + + template + void + parsegrind(string_view msg, Test const& test, bool skip = false) + { + parsegrind(boost::asio::const_buffers_1{ + msg.data(), msg.size()}, test, skip); + } + + template + typename std::enable_if< + is_const_buffer_sequence::value>::type + parsegrind(ConstBufferSequence const& buffers) + { + parsegrind(buffers, [](Parser const&){}); + } + + template + void + parsegrind(string_view msg) + { + parsegrind(msg, [](Parser const&){}); + } + + template + void + failgrind(string_view msg, error_code const& result) + { + for(std::size_t i = 1; i < msg.size() - 1; ++i) + { + Parser p; + p.eager(true); + error_code ec; + consuming_buffers cb{ + boost::in_place_init, msg.data(), msg.size()}; + auto n = p.put(buffer_prefix(i, cb), ec); + if(ec == result) + { + pass(); + continue; + } + if(! BEAST_EXPECTS( + ec == error::need_more, ec.message())) + continue; + if(! BEAST_EXPECT(! p.is_done())) + continue; + cb.consume(n); + n = p.put(cb, ec); + if(! ec) + p.put_eof(ec); + BEAST_EXPECTS(ec == result, ec.message()); + } + for(std::size_t i = 1; i < msg.size() - 1; ++i) + { + Parser p; + p.eager(true); + error_code ec; + p.put(buffer_cat( + boost::asio::const_buffers_1{msg.data(), i}, + boost::asio::const_buffers_1{ + msg.data() + i, msg.size() - i}), ec); + if(! ec) + p.put_eof(ec); + BEAST_EXPECTS(ec == result, ec.message()); + } + } + + //-------------------------------------------------------------------------- + + void + testFlatten() + { + parsegrind>( + "GET / HTTP/1.1\r\n" + "\r\n" + ); + parsegrind>( + "POST / HTTP/1.1\r\n" + "Content-Length: 5\r\n" + "\r\n" + "*****" + ); + parsegrind>( + "HTTP/1.1 403 Not Found\r\n" + "\r\n" + ); + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Content-Length: 5\r\n" + "\r\n" + "*****" + ); + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "5;x\r\n*****\r\n" + "0\r\nMD5: 0xff30\r\n" + "\r\n" + ); + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "\r\n" + "*****" + ); + } + + void + testObsFold() + { + auto const check = + [&](std::string const& s, string_view value) + { + std::string m = + "GET / HTTP/1.1\r\n" + "f: " + s + "\r\n" + "\r\n"; + parsegrind>(m, + [&](parser const& p) + { + BEAST_EXPECT(p.get()["f"] == value); + }); + }; + check("x", "x"); + check(" x", "x"); + check("\tx", "x"); + check(" \tx", "x"); + check("\t x", "x"); + check("x ", "x"); + check(" x\t", "x"); + check("\tx \t", "x"); + check(" \tx\t ", "x"); + check("\t x \t ", "x"); + check("\r\n x", "x"); + check(" \r\n x", "x"); + check(" \r\n\tx", "x"); + check(" \r\n\t x", "x"); + check(" \r\n \tx", "x"); + check(" \r\n \r\n \r\n x \t", "x"); + check("xy", "xy"); + check("\r\n x", "x"); + check("\r\n x", "x"); + check("\r\n xy", "xy"); + check("\r\n \r\n \r\n x", "x"); + check("\r\n \r\n \r\n xy", "xy"); + check("x\r\n y", "x y"); + check("x\r\n y\r\n z ", "x y z"); + } + + // Check that all callbacks are invoked + void + testCallbacks() + { + parsegrind>( + "GET / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*", + [&](test_parser const& p) + { + BEAST_EXPECT(p.got_on_begin == 1); + BEAST_EXPECT(p.got_on_field == 2); + BEAST_EXPECT(p.got_on_header == 1); + BEAST_EXPECT(p.got_on_body == 1); + BEAST_EXPECT(p.got_on_chunk == 0); + BEAST_EXPECT(p.got_on_complete == 1); + }); + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*", + [&](test_parser const& p) + { + BEAST_EXPECT(p.got_on_begin == 1); + BEAST_EXPECT(p.got_on_field == 2); + BEAST_EXPECT(p.got_on_header == 1); + BEAST_EXPECT(p.got_on_body == 1); + BEAST_EXPECT(p.got_on_chunk == 0); + BEAST_EXPECT(p.got_on_complete == 1); + }); + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "1\r\n*\r\n" + "0\r\n\r\n", + [&](test_parser const& p) + { + BEAST_EXPECT(p.got_on_begin == 1); + BEAST_EXPECT(p.got_on_field == 2); + BEAST_EXPECT(p.got_on_header == 1); + BEAST_EXPECT(p.got_on_body == 1); + BEAST_EXPECT(p.got_on_chunk == 1); + BEAST_EXPECT(p.got_on_complete == 1); + }); + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "1;x\r\n*\r\n" + "0\r\n\r\n", + [&](test_parser const& p) + { + BEAST_EXPECT(p.got_on_begin == 1); + BEAST_EXPECT(p.got_on_field == 2); + BEAST_EXPECT(p.got_on_header == 1); + BEAST_EXPECT(p.got_on_body == 1); + BEAST_EXPECT(p.got_on_chunk == 1); + BEAST_EXPECT(p.got_on_complete == 1); + }); + } + + void + testRequestLine() + { + using P = test_parser; + + parsegrind

("GET /x HTTP/1.0\r\n\r\n"); + parsegrind

("!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz / HTTP/1.0\r\n\r\n"); + parsegrind

("GET / HTTP/1.0\r\n\r\n", expect_version{*this, 10}); + parsegrind

("G / HTTP/1.1\r\n\r\n", expect_version{*this, 11}); + // VFALCO TODO various forms of good request-target (uri) + + failgrind

("\tGET / HTTP/1.0\r\n" "\r\n", error::bad_method); + failgrind

("GET\x01 / HTTP/1.0\r\n" "\r\n", error::bad_method); + failgrind

("GET / HTTP/1.0\r\n" "\r\n", error::bad_target); + failgrind

("GET \x01 HTTP/1.0\r\n" "\r\n", error::bad_target); + failgrind

("GET /\x01 HTTP/1.0\r\n" "\r\n", error::bad_target); + // VFALCO TODO various forms of bad request-target (uri) + failgrind

("GET / HTTP/1.0\r\n" "\r\n", error::bad_version); + failgrind

("GET / _TTP/1.0\r\n" "\r\n", error::bad_version); + failgrind

("GET / H_TP/1.0\r\n" "\r\n", error::bad_version); + failgrind

("GET / HT_P/1.0\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTT_/1.0\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP_1.0\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/01.2\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/3.45\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/67.89\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/x.0\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/1.x\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/1.0 \r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/1_0\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/1.0\n\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/1.0\n\r\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/1.0\r\r\n" "\r\n", error::bad_version); + } + + void + testStatusLine() + { + using P = test_parser; + + parsegrind

("HTTP/1.0 000 OK\r\n" "\r\n", expect_status{*this, 0}); + parsegrind

("HTTP/1.1 012 OK\r\n" "\r\n", expect_status{*this, 12}); + parsegrind

("HTTP/1.0 345 OK\r\n" "\r\n", expect_status{*this, 345}); + parsegrind

("HTTP/1.0 678 OK\r\n" "\r\n", expect_status{*this, 678}); + parsegrind

("HTTP/1.0 999 OK\r\n" "\r\n", expect_status{*this, 999}); + parsegrind

("HTTP/1.0 200 \tX\r\n" "\r\n", expect_version{*this, 10}); + parsegrind

("HTTP/1.1 200 X\r\n" "\r\n", expect_version{*this, 11}); + parsegrind

("HTTP/1.0 200 \r\n" "\r\n"); + parsegrind

("HTTP/1.1 200 X \r\n" "\r\n"); + parsegrind

("HTTP/1.1 200 X\t\r\n" "\r\n"); + parsegrind

("HTTP/1.1 200 \x80\x81...\xfe\xff\r\n\r\n"); + parsegrind

("HTTP/1.1 200 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\r\n\r\n"); + + failgrind

("\rHTTP/1.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("\nHTTP/1.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

(" HTTP/1.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("_TTP/1.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("H_TP/1.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HT_P/1.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTT_/1.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTTP_1.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTTP/01.2 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTTP/3.45 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTTP/67.89 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTTP/x.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTTP/1.x 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTTP/1_0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTTP/1.0 200 OK\r\n" "\r\n", error::bad_status); + failgrind

("HTTP/1.0 0 OK\r\n" "\r\n", error::bad_status); + failgrind

("HTTP/1.0 12 OK\r\n" "\r\n", error::bad_status); + failgrind

("HTTP/1.0 3456 OK\r\n" "\r\n", error::bad_status); + failgrind

("HTTP/1.0 200\r\n" "\r\n", error::bad_status); + failgrind

("HTTP/1.0 200 \n\r\n" "\r\n", error::bad_reason); + failgrind

("HTTP/1.0 200 \x01\r\n" "\r\n", error::bad_reason); + failgrind

("HTTP/1.0 200 \x7f\r\n" "\r\n", error::bad_reason); + failgrind

("HTTP/1.0 200 OK\n\r\n" "\r\n", error::bad_reason); + failgrind

("HTTP/1.0 200 OK\r\r\n" "\r\n", error::bad_line_ending); + } + + void + testFields() + { + auto const m = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\n" + s + "\r\n"; + }; + + using P = test_parser; + + parsegrind

(m("f:\r\n")); + parsegrind

(m("f: \r\n")); + parsegrind

(m("f:\t\r\n")); + parsegrind

(m("f: \t\r\n")); + parsegrind

(m("f: v\r\n")); + parsegrind

(m("f:\tv\r\n")); + parsegrind

(m("f:\tv \r\n")); + parsegrind

(m("f:\tv\t\r\n")); + parsegrind

(m("f:\tv\t \r\n")); + parsegrind

(m("f:\r\n \r\n")); + parsegrind

(m("f:v\r\n")); + parsegrind

(m("f: v\r\n u\r\n")); + parsegrind

(m("!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz: v\r\n")); + parsegrind

(m("f: !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x80\x81...\xfe\xff\r\n")); + + failgrind

(m(" f: v\r\n"), error::bad_field); + failgrind

(m("\tf: v\r\n"), error::bad_field); + failgrind

(m("f : v\r\n"), error::bad_field); + failgrind

(m("f\t: v\r\n"), error::bad_field); + failgrind

(m("f: \n\r\n"), error::bad_value); + failgrind

(m("f: v\r \r\n"), error::bad_line_ending); + failgrind

(m("f: \r v\r\n"), error::bad_line_ending); + failgrind

( + "GET / HTTP/1.1\r\n" + "\r \n\r\n" + "\r\n", error::bad_line_ending); + } + + void + testConnectionField() + { + auto const m = [](std::string const& s) + { return "GET / HTTP/1.1\r\n" + s + "\r\n"; }; + auto const cn = [](std::string const& s) + { return "GET / HTTP/1.1\r\nConnection: " + s + "\r\n"; }; + #if 0 + auto const keepalive = [&](bool v) + { //return keepalive_f{*this, v}; return true; }; + #endif + + using P = test_parser; + + parsegrind

(cn("close\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn(",close\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn(" close\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("\tclose\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("close,\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("close\t\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("close\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn(" ,\t,,close,, ,\t,,\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("\r\n close\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("close\r\n \r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("any,close\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("close,any\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("any\r\n ,close\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("close\r\n ,any\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("close,close\r\n"), expect_flags{*this, parse_flag::connection_close}); // weird but allowed + + parsegrind

(cn("keep-alive\r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + parsegrind

(cn("keep-alive \r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + parsegrind

(cn("keep-alive\t \r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + parsegrind

(cn("keep-alive\t ,x\r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + parsegrind

(cn("\r\n keep-alive \t\r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + parsegrind

(cn("keep-alive \r\n \t \r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + parsegrind

(cn("keep-alive\r\n \r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + + parsegrind

(cn("upgrade\r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + parsegrind

(cn("upgrade \r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + parsegrind

(cn("upgrade\t \r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + parsegrind

(cn("upgrade\t ,x\r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + parsegrind

(cn("\r\n upgrade \t\r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + parsegrind

(cn("upgrade \r\n \t \r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + parsegrind

(cn("upgrade\r\n \r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + + // VFALCO What's up with these? + //parsegrind

(cn("close,keep-alive\r\n"), expect_flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive}); + parsegrind

(cn("upgrade,keep-alive\r\n"), expect_flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive}); + parsegrind

(cn("upgrade,\r\n keep-alive\r\n"), expect_flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive}); + //parsegrind

(cn("close,keep-alive,upgrade\r\n"), expect_flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive | parse_flag::connection_upgrade}); + + parsegrind

("GET / HTTP/1.1\r\n\r\n", expect_keepalive(*this, true)); + parsegrind

("GET / HTTP/1.0\r\n\r\n", expect_keepalive(*this, false)); + parsegrind

("GET / HTTP/1.0\r\n" + "Connection: keep-alive\r\n\r\n", expect_keepalive(*this, true)); + parsegrind

("GET / HTTP/1.1\r\n" + "Connection: close\r\n\r\n", expect_keepalive(*this, false)); + + parsegrind

(cn("x\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("x,y\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("x ,y\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("x\t,y\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("keep\r\n"), expect_flags{*this, 0}); + parsegrind

(cn(",keep\r\n"), expect_flags{*this, 0}); + parsegrind

(cn(" keep\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("\tnone\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("keep,\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("keep\t\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("keep\r\n"), expect_flags{*this, 0}); + parsegrind

(cn(" ,\t,,keep,, ,\t,,\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("\r\n keep\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("keep\r\n \r\n"), expect_flags{*this, 0}); + parsegrind

(cn("closet\r\n"), expect_flags{*this, 0}); + parsegrind

(cn(",closet\r\n"), expect_flags{*this, 0}); + parsegrind

(cn(" closet\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("\tcloset\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("closet,\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("closet\t\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("closet\r\n"), expect_flags{*this, 0}); + parsegrind

(cn(" ,\t,,closet,, ,\t,,\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("\r\n closet\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("closet\r\n \r\n"), expect_flags{*this, 0}); + parsegrind

(cn("clog\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("key\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("uptown\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("keeper\r\n \r\n"), expect_flags{*this, 0}); + parsegrind

(cn("keep-alively\r\n \r\n"), expect_flags{*this, 0}); + parsegrind

(cn("up\r\n \r\n"), expect_flags{*this, 0}); + parsegrind

(cn("upgrader\r\n \r\n"), expect_flags{*this, 0}); + parsegrind

(cn("none\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("\r\n none\r\n"), expect_flags{*this, 0}); + + parsegrind

(m("ConnectioX: close\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Condor: close\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Connect: close\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Connections: close\r\n"), expect_flags{*this, 0}); + + parsegrind

(m("Proxy-Connection: close\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(m("Proxy-Connection: keep-alive\r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + parsegrind

(m("Proxy-Connection: upgrade\r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + parsegrind

(m("Proxy-ConnectioX: none\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Proxy-Connections: 1\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Proxy-Connotes: see-also\r\n"), expect_flags{*this, 0}); + + failgrind

(cn("[\r\n"), error::bad_value); + failgrind

(cn("close[\r\n"), error::bad_value); + failgrind

(cn("close [\r\n"), error::bad_value); + failgrind

(cn("close, upgrade [\r\n"), error::bad_value); + failgrind

(cn("upgrade[]\r\n"), error::bad_value); + failgrind

(cn("keep\r\n -alive\r\n"), error::bad_value); + failgrind

(cn("keep-alive[\r\n"), error::bad_value); + failgrind

(cn("keep-alive []\r\n"), error::bad_value); + failgrind

(cn("no[ne]\r\n"), error::bad_value); + } + + void + testContentLengthField() + { + using P = test_parser; + auto const c = [](std::string const& s) + { return "GET / HTTP/1.1\r\nContent-Length: " + s + "\r\n"; }; + auto const m = [](std::string const& s) + { return "GET / HTTP/1.1\r\n" + s + "\r\n"; }; + auto const check = + [&](std::string const& s, std::uint64_t v) + { + parsegrind

(c(s), + [&](P const& p) + { + BEAST_EXPECT(p.content_length()); + BEAST_EXPECT(p.content_length() && *p.content_length() == v); + }, true); + }; + + check("0\r\n", 0); + check("00\r\n", 0); + check("1\r\n", 1); + check("01\r\n", 1); + check("9\r\n", 9); + check("42 \r\n", 42); + check("42\t\r\n", 42); + check("42 \t \r\n", 42); + check("42\r\n \t \r\n", 42); + + parsegrind

(m("Content-LengtX: 0\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Content-Lengths: many\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Content: full\r\n"), expect_flags{*this, 0}); + + failgrind

(c("\r\n"), error::bad_content_length); + failgrind

(c("18446744073709551616\r\n"), error::bad_content_length); + failgrind

(c("0 0\r\n"), error::bad_content_length); + failgrind

(c("0 1\r\n"), error::bad_content_length); + failgrind

(c(",\r\n"), error::bad_content_length); + failgrind

(c("0,\r\n"), error::bad_content_length); + failgrind

(m( + "Content-Length: 0\r\nContent-Length: 0\r\n"), error::bad_content_length); + } + + void + testTransferEncodingField() + { + auto const m = [](std::string const& s) + { return "GET / HTTP/1.1\r\n" + s + "\r\n"; }; + auto const ce = [](std::string const& s) + { return "GET / HTTP/1.1\r\nTransfer-Encoding: " + s + "\r\n0\r\n\r\n"; }; + auto const te = [](std::string const& s) + { return "GET / HTTP/1.1\r\nTransfer-Encoding: " + s + "\r\n"; }; + + using P = test_parser; + + parsegrind

(ce("chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("chunked \r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("chunked\t\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("chunked \t\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce(" chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("\tchunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("chunked,\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("chunked ,\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("chunked, \r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce(",chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce(", chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce(" ,chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("chunked\r\n \r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce(",\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("\r\n ,chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce(",\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("gzip, chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("gzip, chunked \r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("gzip, \r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + + // Technically invalid but beyond the parser's scope to detect + // VFALCO Look into this + //parsegrind

(ce("custom;key=\",chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + + parsegrind

(te("gzip\r\n"), expect_flags{*this, 0}); + parsegrind

(te("chunked, gzip\r\n"), expect_flags{*this, 0}); + parsegrind

(te("chunked\r\n , gzip\r\n"), expect_flags{*this, 0}); + parsegrind

(te("chunked,\r\n gzip\r\n"), expect_flags{*this, 0}); + parsegrind

(te("chunked,\r\n ,gzip\r\n"), expect_flags{*this, 0}); + parsegrind

(te("bigchunked\r\n"), expect_flags{*this, 0}); + parsegrind

(te("chunk\r\n ked\r\n"), expect_flags{*this, 0}); + parsegrind

(te("bar\r\n ley chunked\r\n"), expect_flags{*this, 0}); + parsegrind

(te("barley\r\n chunked\r\n"), expect_flags{*this, 0}); + + parsegrind

(m("Transfer-EncodinX: none\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Transfer-Encodings: 2\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Transfer-Encoded: false\r\n"), expect_flags{*this, 0}); + + failgrind>( + "HTTP/1.1 200 OK\r\n" + "Content-Length: 1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n", error::bad_transfer_encoding); + } + + void + testUpgradeField() + { + auto const m = [](std::string const& s) + { return "GET / HTTP/1.1\r\n" + s + "\r\n"; }; + + using P = test_parser; + + parsegrind

(m("Upgrade:\r\n"), expect_flags{*this, parse_flag::upgrade}); + parsegrind

(m("Upgrade: \r\n"), expect_flags{*this, parse_flag::upgrade}); + parsegrind

(m("Upgrade: yes\r\n"), expect_flags{*this, parse_flag::upgrade}); + + parsegrind

(m("Up: yes\r\n"), expect_flags{*this, 0}); + parsegrind

(m("UpgradX: none\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Upgrades: 2\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Upsample: 4x\r\n"), expect_flags{*this, 0}); + + parsegrind

( + "GET / HTTP/1.1\r\n" + "Connection: upgrade\r\n" + "Upgrade: WebSocket\r\n" + "\r\n", + [&](P const& p) + { + BEAST_EXPECT(p.is_upgrade()); + }); + } + + void + testPartial() + { + // Make sure the slow-loris defense works and that + // we don't get duplicate or missing fields on a split. + parsegrind>( + "GET / HTTP/1.1\r\n" + "a: 0\r\n" + "b: 1\r\n" + "c: 2\r\n" + "d: 3\r\n" + "e: 4\r\n" + "f: 5\r\n" + "g: 6\r\n" + "h: 7\r\n" + "i: 8\r\n" + "j: 9\r\n" + "\r\n", + [&](test_parser const& p) + { + BEAST_EXPECT(p.fields.size() == 10); + BEAST_EXPECT(p.fields.at("a") == "0"); + BEAST_EXPECT(p.fields.at("b") == "1"); + BEAST_EXPECT(p.fields.at("c") == "2"); + BEAST_EXPECT(p.fields.at("d") == "3"); + BEAST_EXPECT(p.fields.at("e") == "4"); + BEAST_EXPECT(p.fields.at("f") == "5"); + BEAST_EXPECT(p.fields.at("g") == "6"); + BEAST_EXPECT(p.fields.at("h") == "7"); + BEAST_EXPECT(p.fields.at("i") == "8"); + BEAST_EXPECT(p.fields.at("j") == "9"); + }); + } + + void + testLimits() + { + { + multi_buffer b; + ostream(b) << + "POST / HTTP/1.1\r\n" + "Content-Length: 2\r\n" + "\r\n" + "**"; + error_code ec; + test_parser p; + p.header_limit(10); + p.eager(true); + p.put(b.data(), ec); + BEAST_EXPECTS(ec == error::header_limit, ec.message()); + } + { + multi_buffer b; + ostream(b) << + "POST / HTTP/1.1\r\n" + "Content-Length: 2\r\n" + "\r\n" + "**"; + error_code ec; + test_parser p; + p.body_limit(1); + p.eager(true); + p.put(b.data(), ec); + BEAST_EXPECTS(ec == error::body_limit, ec.message()); + } + { + multi_buffer b; + ostream(b) << + "HTTP/1.1 200 OK\r\n" + "\r\n" + "**"; + error_code ec; + test_parser p; + p.body_limit(1); + p.eager(true); + p.put(b.data(), ec); + BEAST_EXPECTS(ec == error::body_limit, ec.message()); + } + { + multi_buffer b; + ostream(b) << + "POST / HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "2\r\n" + "**\r\n" + "0\r\n\r\n"; + error_code ec; + test_parser p; + p.body_limit(1); + p.eager(true); + p.put(b.data(), ec); + BEAST_EXPECTS(ec == error::body_limit, ec.message()); + } + } + + //-------------------------------------------------------------------------- + + static + boost::asio::const_buffers_1 + buf(string_view s) + { + return {s.data(), s.size()}; + } + + template + std::size_t + feed(ConstBufferSequence const& buffers, + basic_parser& p, error_code& ec) + { + p.eager(true); + return p.put(buffers, ec); + } + + void + testBody() + { + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "Content-Type: application/octet-stream\r\n" + "\r\n" + "4\r\nabcd\r\n" + "0\r\n\r\n" + ,[&](test_parser const& p) + { + BEAST_EXPECT(p.body == "abcd"); + }); + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "Expect: Expires, MD5-Fingerprint\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "5\r\n" + "*****\r\n" + "2;a;b=1;c=\"2\"\r\n" + "--\r\n" + "0;d;e=3;f=\"4\"\r\n" + "Expires: never\r\n" + "MD5-Fingerprint: -\r\n" + "\r\n" + ,[&](test_parser const& p) + { + BEAST_EXPECT(p.body == "*****--"); + }); + + parsegrind>( + "GET / HTTP/1.1\r\n" + "Content-Length: 1\r\n" + "\r\n" + "1", + expect_body(*this, "1")); + + parsegrind>( + "HTTP/1.0 200 OK\r\n" + "\r\n" + "hello", + expect_body(*this, "hello")); + + parsegrind>(buffer_cat( + buf("GET / HTTP/1.1\r\n" + "Content-Length: 10\r\n" + "\r\n"), + buf("12"), + buf("345"), + buf("67890"))); + + // request without Content-Length or + // Transfer-Encoding: chunked has no body. + { + error_code ec; + test_parser p; + feed(buf( + "GET / HTTP/1.0\r\n" + "\r\n" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_done()); + } + { + error_code ec; + test_parser p; + feed(buf( + "GET / HTTP/1.1\r\n" + "\r\n" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_done()); + } + + // response without Content-Length or + // Transfer-Encoding: chunked requires eof. + { + error_code ec; + test_parser p; + feed(buf( + "HTTP/1.0 200 OK\r\n" + "\r\n" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(! p.is_done()); + BEAST_EXPECT(p.need_eof()); + } + + // 304 "Not Modified" response does not require eof + { + error_code ec; + test_parser p; + feed(buf( + "HTTP/1.0 304 Not Modified\r\n" + "\r\n" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_done()); + } + + // Chunked response does not require eof + { + error_code ec; + test_parser p; + feed(buf( + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(! p.is_done()); + feed(buf( + "0\r\n\r\n" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_done()); + } + + // restart: 1.0 assumes Connection: close + { + error_code ec; + test_parser p; + feed(buf( + "GET / HTTP/1.0\r\n" + "\r\n" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_done()); + } + + // restart: 1.1 assumes Connection: keep-alive + { + error_code ec; + test_parser p; + feed(buf( + "GET / HTTP/1.1\r\n" + "\r\n" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_done()); + } + + failgrind>( + "GET / HTTP/1.1\r\n" + "Content-Length: 1\r\n" + "\r\n", + error::partial_message); + } + + //-------------------------------------------------------------------------- + + // https://github.com/vinniefalco/Beast/issues/430 + void + testIssue430() + { + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "Content-Type: application/octet-stream\r\n" + "\r\n" + "4\r\nabcd\r\n" + "0\r\n\r\n"); + } + + // https://github.com/vinniefalco/Beast/issues/452 + void + testIssue452() + { + error_code ec; + test_parser p; + p.eager(true); + string_view s = + "GET / HTTP/1.1\r\n" + "\r\n" + "die!"; + p.put(boost::asio::buffer( + s.data(), s.size()), ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + BEAST_EXPECT(p.is_done()); + } + + // https://github.com/vinniefalco/Beast/issues/496 + void + testIssue496() + { + // The bug affected hex parsing with leading zeroes + using P = test_parser; + parsegrind

( + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "Content-Type: application/octet-stream\r\n" + "\r\n" + "0004\r\nabcd\r\n" + "0\r\n\r\n" + ,[&](P const& p) + { + BEAST_EXPECT(p.body == "abcd"); + }); + } + + //-------------------------------------------------------------------------- + + void + testFuzz1() + { + // crash_00cda0b02d5166bd1039ddb3b12618cd80da75f3 + unsigned char buf[] ={ + 0x4C,0x4F,0x43,0x4B,0x20,0x2F,0x25,0x65,0x37,0x6C,0x59,0x3B,0x2F,0x3B,0x3B,0x25,0x30,0x62,0x38,0x3D,0x70,0x2F,0x72,0x20, + 0x48,0x54,0x54,0x50,0x2F,0x31,0x2E,0x31,0x0D,0x0A,0x41,0x63,0x63,0x65,0x70,0x74,0x2D,0x45,0x6E,0x63,0x6F,0x64,0x69,0x6E, + 0x67,0x3A,0x0D,0x0A,0x09,0x20,0xEE,0x0D,0x0A,0x4F,0x72,0x69,0x67,0x69,0x6E,0x61,0x6C,0x2D,0x4D,0x65,0x73,0x73,0x61,0x67, + 0x65,0x2D,0x49,0x44,0x3A,0xEB,0x09,0x09,0x09,0x09,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x3A,0x20,0x0D,0x0A,0x09,0x20, + 0xF7,0x44,0x9B,0xA5,0x06,0x9F,0x0D,0x0A,0x52,0x65,0x73,0x65,0x6E,0x74,0x2D,0x44,0x61,0x74,0x65,0x3A,0xF4,0x0D,0x0A,0x41, + 0x6C,0x74,0x2D,0x53,0x76,0x63,0x3A,0x20,0x0D,0x0A,0x54,0x72,0x61,0x69,0x6C,0x65,0x72,0x3A,0x20,0x20,0x09,0x20,0x20,0x20, + 0x0D,0x0A,0x4C,0x69,0x73,0x74,0x2D,0x49,0x44,0x3A,0xA6,0x6B,0x86,0x09,0x09,0x20,0x09,0x0D,0x0A,0x41,0x6C,0x74,0x65,0x72, + 0x6E,0x61,0x74,0x65,0x2D,0x52,0x65,0x63,0x69,0x70,0x69,0x65,0x6E,0x74,0x3A,0xF3,0x13,0xE3,0x22,0x9D,0xEF,0xFB,0x84,0x71, + 0x4A,0xCC,0xBC,0x96,0xF7,0x5B,0x72,0xF1,0xF2,0x0D,0x0A,0x4C,0x6F,0x63,0x61,0x74,0x69,0x6F,0x6E,0x3A,0x20,0x0D,0x0A,0x41, + 0x63,0x63,0x65,0x70,0x74,0x2D,0x41,0x64,0x64,0x69,0x74,0x69,0x6F,0x6E,0x73,0x3A,0x20,0x0D,0x0A,0x4D,0x4D,0x48,0x53,0x2D, + 0x4F,0x72,0x69,0x67,0x69,0x6E,0x61,0x74,0x6F,0x72,0x2D,0x50,0x4C,0x41,0x44,0x3A,0x20,0x0D,0x0A,0x4F,0x72,0x69,0x67,0x69, + 0x6E,0x61,0x6C,0x2D,0x53,0x65,0x6E,0x64,0x65,0x72,0x3A,0x20,0x0D,0x0A,0x4F,0x72,0x69,0x67,0x69,0x6E,0x61,0x6C,0x2D,0x53, + 0x65,0x6E,0x64,0x65,0x72,0x3A,0x0D,0x0A,0x50,0x49,0x43,0x53,0x2D,0x4C,0x61,0x62,0x65,0x6C,0x3A,0x0D,0x0A,0x20,0x09,0x0D, + 0x0A,0x49,0x66,0x3A,0x20,0x40,0xC1,0x50,0x5C,0xD6,0xC3,0x86,0xFC,0x8D,0x5C,0x7C,0x96,0x45,0x0D,0x0A,0x4D,0x4D,0x48,0x53, + 0x2D,0x45,0x78,0x65,0x6D,0x70,0x74,0x65,0x64,0x2D,0x41,0x64,0x64,0x72,0x65,0x73,0x73,0x3A,0x0D,0x0A,0x49,0x6E,0x6A,0x65, + 0x63,0x74,0x69,0x6F,0x6E,0x2D,0x49,0x6E,0x66,0x6F,0x3A,0x20,0x0D,0x0A,0x43,0x6F,0x6E,0x74,0x65,0x74,0x6E,0x2D,0x4C,0x65, + 0x6E,0x67,0x74,0x68,0x3A,0x20,0x30,0x0D,0x0A,0x0D,0x0A + }; + + error_code ec; + test_parser p; + feed(boost::asio::buffer(buf, sizeof(buf)), p, ec); + BEAST_EXPECT(ec); + } + + //-------------------------------------------------------------------------- + + void + run() override + { + testFlatten(); + testObsFold(); + testCallbacks(); + testRequestLine(); + testStatusLine(); + testFields(); + testConnectionField(); + testContentLengthField(); + testTransferEncodingField(); + testUpgradeField(); + testPartial(); + testLimits(); + testBody(); + testIssue430(); + testIssue452(); + testIssue496(); + testFuzz1(); + } +}; + +BEAST_DEFINE_TESTSUITE(basic_parser,http,beast); + +} // http +} // beast diff --git a/test/http/basic_parser_v1.cpp b/test/http/basic_parser_v1.cpp deleted file mode 100644 index 5c88a98dee..0000000000 --- a/test/http/basic_parser_v1.cpp +++ /dev/null @@ -1,1175 +0,0 @@ -// -// 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) -// - -// Test that header file is self-contained. -#include - -#include "fail_parser.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -class basic_parser_v1_test : public beast::unit_test::suite -{ -public: - struct cb_req_checker - { - bool method = false; - bool uri = false; - bool request = false; - }; - - struct cb_res_checker - { - bool reason = false; - bool response = false; - }; - - template - struct cb_checker - : public basic_parser_v1> - , std::conditional::type - - { - bool start = false; - bool field = false; - bool value = false; - bool fields = false; - bool _body_what = false; - bool body = false; - bool complete = false; - - private: - friend class basic_parser_v1>; - - void on_start(error_code&) - { - this->start = true; - } - void on_method(boost::string_ref const&, error_code&) - { - this->method = true; - } - void on_uri(boost::string_ref const&, error_code&) - { - this->uri = true; - } - void on_reason(boost::string_ref const&, error_code&) - { - this->reason = true; - } - void on_request(error_code&) - { - this->request = true; - } - void on_response(error_code&) - { - this->response = true; - } - void on_field(boost::string_ref const&, error_code&) - { - field = true; - } - void on_value(boost::string_ref const&, error_code&) - { - value = true; - } - void - on_header(std::uint64_t, error_code&) - { - fields = true; - } - body_what - on_body_what(std::uint64_t, error_code&) - { - _body_what = true; - return body_what::normal; - } - void on_body(boost::string_ref const&, error_code&) - { - body = true; - } - void on_complete(error_code&) - { - complete = true; - } - }; - - // Check that all callbacks are invoked - void - testCallbacks() - { - using boost::asio::buffer; - { - cb_checker p; - error_code ec; - std::string const s = - "GET / HTTP/1.1\r\n" - "User-Agent: test\r\n" - "Content-Length: 1\r\n" - "\r\n" - "*"; - p.write(buffer(s), ec); - if(BEAST_EXPECT(! ec)) - { - BEAST_EXPECT(p.start); - BEAST_EXPECT(p.method); - BEAST_EXPECT(p.uri); - BEAST_EXPECT(p.request); - BEAST_EXPECT(p.field); - BEAST_EXPECT(p.value); - BEAST_EXPECT(p.fields); - BEAST_EXPECT(p._body_what); - BEAST_EXPECT(p.body); - BEAST_EXPECT(p.complete); - } - } - { - cb_checker p; - error_code ec; - std::string const s = - "HTTP/1.1 200 OK\r\n" - "Server: test\r\n" - "Content-Length: 1\r\n" - "\r\n" - "*"; - p.write(buffer(s), ec); - if(BEAST_EXPECT(! ec)) - { - BEAST_EXPECT(p.start); - BEAST_EXPECT(p.reason); - BEAST_EXPECT(p.response); - BEAST_EXPECT(p.field); - BEAST_EXPECT(p.value); - BEAST_EXPECT(p.fields); - BEAST_EXPECT(p.body); - BEAST_EXPECT(p.complete); - } - } - } - - //-------------------------------------------------------------------------- - - template - static - void - for_split(boost::string_ref const& s, F const& f) - { - using boost::asio::buffer; - using boost::asio::buffer_copy; - for(std::size_t i = 0; i < s.size(); ++i) - { - // Use separately allocated buffers so - // address sanitizer has something to chew on. - // - auto const n1 = s.size() - i; - auto const n2 = i; - std::unique_ptr p1(new char[n1]); - std::unique_ptr p2(new char[n2]); - buffer_copy(buffer(p1.get(), n1), buffer(s.data(), n1)); - buffer_copy(buffer(p2.get(), n2), buffer(s.data() + n1, n2)); - f( - boost::string_ref{p1.get(), n1}, - boost::string_ref{p2.get(), n2}); - } - } - - struct none - { - template - void - operator()(Parser const&) const - { - } - }; - - template - void - good(body_what onBodyRv, std::string const& s, F const& f) - { - using boost::asio::buffer; - for_split(s, - [&](boost::string_ref const& s1, boost::string_ref const& s2) - { - static std::size_t constexpr Limit = 200; - std::size_t n; - for(n = 0; n < Limit; ++n) - { - test::fail_counter fc(n); - fail_parser p(fc); - p.on_body_rv(onBodyRv); - error_code ec; - p.write(buffer(s1.data(), s1.size()), ec); - if(ec == test::error::fail_error) - continue; - if(! BEAST_EXPECT(! ec)) - break; - if(! BEAST_EXPECT(s2.empty() || ! p.complete())) - break; - p.write(buffer(s2.data(), s2.size()), ec); - if(ec == test::error::fail_error) - continue; - if(! BEAST_EXPECT(! ec)) - break; - p.write_eof(ec); - if(ec == test::error::fail_error) - continue; - if(! BEAST_EXPECT(! ec)) - break; - BEAST_EXPECT(p.complete()); - f(p); - break; - } - BEAST_EXPECT(n < Limit); - }); - } - - template - void - good(std::string const& s, F const& f = {}) - { - return good(body_what::normal, s, f); - } - - template - void - bad(body_what onBodyRv, std::string const& s, error_code ev) - { - using boost::asio::buffer; - for_split(s, - [&](boost::string_ref const& s1, boost::string_ref const& s2) - { - static std::size_t constexpr Limit = 200; - std::size_t n; - for(n = 0; n < Limit; ++n) - { - test::fail_counter fc(n); - fail_parser p(fc); - p.on_body_rv(onBodyRv); - error_code ec; - p.write(buffer(s1.data(), s1.size()), ec); - if(ec == test::error::fail_error) - continue; - if(ec) - { - BEAST_EXPECT((ec && ! ev) || ec == ev); - break; - } - if(! BEAST_EXPECT(! p.complete())) - break; - if(! s2.empty()) - { - p.write(buffer(s2.data(), s2.size()), ec); - if(ec == test::error::fail_error) - continue; - if(ec) - { - BEAST_EXPECT((ec && ! ev) || ec == ev); - break; - } - if(! BEAST_EXPECT(! p.complete())) - break; - } - p.write_eof(ec); - if(ec == test::error::fail_error) - continue; - BEAST_EXPECT(! p.complete()); - BEAST_EXPECT((ec && ! ev) || ec == ev); - break; - } - BEAST_EXPECT(n < Limit); - }); - } - - template - void - bad(std::string const& s, error_code ev = {}) - { - return bad(body_what::normal, s, ev); - } - - //-------------------------------------------------------------------------- - - class version - { - suite& s_; - unsigned major_; - unsigned minor_; - - public: - version(suite& s, unsigned major, unsigned minor) - : s_(s) - , major_(major) - , minor_(minor) - { - } - - template - void - operator()(Parser const& p) const - { - s_.BEAST_EXPECT(p.http_major() == major_); - s_.BEAST_EXPECT(p.http_minor() == minor_); - } - }; - - class status - { - suite& s_; - unsigned code_; - public: - status(suite& s, int code) - : s_(s) - , code_(code) - { - } - - template - void - operator()(Parser const& p) const - { - s_.BEAST_EXPECT(p.status_code() == code_); - } - }; - - void testRequestLine() - { - /* - request-line = method SP request-target SP HTTP-version CRLF - method = token - request-target = origin-form / absolute-form / authority-form / asterisk-form - HTTP-version = "HTTP/" DIGIT "." DIGIT - */ - good("GET /x HTTP/1.0\r\n\r\n"); - good("!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz / HTTP/1.0\r\n\r\n"); - good("GET / HTTP/1.0\r\n\r\n", version{*this, 1, 0}); - good("G / HTTP/1.1\r\n\r\n", version{*this, 1, 1}); - // VFALCO TODO various forms of good request-target (uri) - good("GET / HTTP/0.1\r\n\r\n", version{*this, 0, 1}); - good("GET / HTTP/2.3\r\n\r\n", version{*this, 2, 3}); - good("GET / HTTP/4.5\r\n\r\n", version{*this, 4, 5}); - good("GET / HTTP/6.7\r\n\r\n", version{*this, 6, 7}); - good("GET / HTTP/8.9\r\n\r\n", version{*this, 8, 9}); - - bad("\tGET / HTTP/1.0\r\n" "\r\n", parse_error::bad_method); - bad("GET\x01 / HTTP/1.0\r\n" "\r\n", parse_error::bad_method); - bad("GET / HTTP/1.0\r\n" "\r\n", parse_error::bad_uri); - bad("GET \x01 HTTP/1.0\r\n" "\r\n", parse_error::bad_uri); - bad("GET /\x01 HTTP/1.0\r\n" "\r\n", parse_error::bad_uri); - // VFALCO TODO various forms of bad request-target (uri) - bad("GET / HTTP/1.0\r\n" "\r\n", parse_error::bad_version); - bad("GET / _TTP/1.0\r\n" "\r\n", parse_error::bad_version); - bad("GET / H_TP/1.0\r\n" "\r\n", parse_error::bad_version); - bad("GET / HT_P/1.0\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTT_/1.0\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP_1.0\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/01.2\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/3.45\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/67.89\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/x.0\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/1.x\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/1.0 \r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/1_0\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/1.0\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/1.0\n\r" "\r\n", parse_error::bad_version); - bad("GET / HTTP/1.0\r\r\n" "\r\n", parse_error::bad_crlf); - - // write a bad request line in 2 pieces - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buffer_cat( - buf("GET / "), buf("_TTP/1.1\r\n"), - buf("\r\n") - ), ec); - BEAST_EXPECT(ec == parse_error::bad_version); - } - } - - void testStatusLine() - { - /* - status-line = HTTP-version SP status-code SP reason-phrase CRLF - HTTP-version = "HTTP/" DIGIT "." DIGIT - status-code = 3DIGIT - reason-phrase = *( HTAB / SP / VCHAR / obs-text ) - */ - good("HTTP/0.1 200 OK\r\n" "\r\n", version{*this, 0, 1}); - good("HTTP/2.3 200 OK\r\n" "\r\n", version{*this, 2, 3}); - good("HTTP/4.5 200 OK\r\n" "\r\n", version{*this, 4, 5}); - good("HTTP/6.7 200 OK\r\n" "\r\n", version{*this, 6, 7}); - good("HTTP/8.9 200 OK\r\n" "\r\n", version{*this, 8, 9}); - good("HTTP/1.0 000 OK\r\n" "\r\n", status{*this, 0}); - good("HTTP/1.1 012 OK\r\n" "\r\n", status{*this, 12}); - good("HTTP/1.0 345 OK\r\n" "\r\n", status{*this, 345}); - good("HTTP/1.0 678 OK\r\n" "\r\n", status{*this, 678}); - good("HTTP/1.0 999 OK\r\n" "\r\n", status{*this, 999}); - good("HTTP/1.0 200 \tX\r\n" "\r\n", version{*this, 1, 0}); - good("HTTP/1.1 200 X\r\n" "\r\n", version{*this, 1, 1}); - good("HTTP/1.0 200 \r\n" "\r\n"); - good("HTTP/1.1 200 X \r\n" "\r\n"); - good("HTTP/1.1 200 X\t\r\n" "\r\n"); - good("HTTP/1.1 200 \x80\x81...\xfe\xff\r\n\r\n"); - good("HTTP/1.1 200 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\r\n\r\n"); - - bad("\rHTTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("\nHTTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad(" HTTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("_TTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("H_TP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HT_P/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTT_/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTTP_1.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTTP/01.2 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTTP/3.45 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTTP/67.89 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTTP/x.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTTP/1.x 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTTP/1_0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_status); - bad("HTTP/1.0 0 OK\r\n" "\r\n", parse_error::bad_status); - bad("HTTP/1.0 12 OK\r\n" "\r\n", parse_error::bad_status); - bad("HTTP/1.0 3456 OK\r\n" "\r\n", parse_error::bad_status); - bad("HTTP/1.0 200\r\n" "\r\n", parse_error::bad_status); - bad("HTTP/1.0 200 \n" "\r\n", parse_error::bad_reason); - bad("HTTP/1.0 200 \x01\r\n" "\r\n", parse_error::bad_reason); - bad("HTTP/1.0 200 \x7f\r\n" "\r\n", parse_error::bad_reason); - bad("HTTP/1.0 200 OK\n" "\r\n", parse_error::bad_reason); - bad("HTTP/1.0 200 OK\r\r\n" "\r\n", parse_error::bad_crlf); - } - - //-------------------------------------------------------------------------- - - void testHeaders() - { - /* - header-field = field-name ":" OWS field-value OWS - field-name = token - field-value = *( field-content / obs-fold ) - field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] - field-vchar = VCHAR / obs-text - obs-fold = CRLF 1*( SP / HTAB ) - ; obsolete line folding - */ - auto const m = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\n" + s + "\r\n"; - }; - good(m("f:\r\n")); - good(m("f: \r\n")); - good(m("f:\t\r\n")); - good(m("f: \t\r\n")); - good(m("f: v\r\n")); - good(m("f:\tv\r\n")); - good(m("f:\tv \r\n")); - good(m("f:\tv\t\r\n")); - good(m("f:\tv\t \r\n")); - good(m("f:\r\n \r\n")); - good(m("f:v\r\n")); - good(m("f: v\r\n u\r\n")); - good(m("!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz: v\r\n")); - good(m("f: !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x80\x81...\xfe\xff\r\n")); - - bad(m(" f: v\r\n"), parse_error::bad_field); - bad(m("\tf: v\r\n"), parse_error::bad_field); - bad(m("f : v\r\n"), parse_error::bad_field); - bad(m("f\t: v\r\n"), parse_error::bad_field); - bad(m("f: \n\r\n"), parse_error::bad_value); - bad(m("f: v\r \r\n"), parse_error::bad_crlf); - bad(m("f: \r v\r\n"), parse_error::bad_crlf); - bad("GET / HTTP/1.1\r\n\r \n", parse_error::bad_crlf); - } - - //-------------------------------------------------------------------------- - - class flags - { - suite& s_; - std::size_t value_; - - public: - flags(suite& s, std::size_t value) - : s_(s) - , value_(value) - { - } - - template - void - operator()(Parser const& p) const - { - s_.BEAST_EXPECT(p.flags() == value_); - } - }; - - class keepalive_f - { - suite& s_; - bool value_; - - public: - keepalive_f(suite& s, bool value) - : s_(s) - , value_(value) - { - } - - template - void - operator()(Parser const& p) const - { - s_.BEAST_EXPECT(p.keep_alive() == value_); - } - }; - - void testConnectionHeader() - { - auto const m = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\n" + s + "\r\n"; - }; - auto const cn = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\nConnection: " + s + "\r\n"; - }; - auto const keepalive = - [&](bool v) - { - return keepalive_f{*this, v}; - }; - - good(cn("close\r\n"), flags{*this, parse_flag::connection_close}); - good(cn(",close\r\n"), flags{*this, parse_flag::connection_close}); - good(cn(" close\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("\tclose\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("close,\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("close\t\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("close\r\n"), flags{*this, parse_flag::connection_close}); - good(cn(" ,\t,,close,, ,\t,,\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("\r\n close\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("close\r\n \r\n"), flags{*this, parse_flag::connection_close}); - good(cn("any,close\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("close,any\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("any\r\n ,close\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("close\r\n ,any\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("close,close\r\n"), flags{*this, parse_flag::connection_close}); // weird but allowed - - good(cn("keep-alive\r\n"), flags{*this, parse_flag::connection_keep_alive}); - good(cn("keep-alive \r\n"), flags{*this, parse_flag::connection_keep_alive}); - good(cn("keep-alive\t \r\n"), flags{*this, parse_flag::connection_keep_alive}); - good(cn("keep-alive\t ,x\r\n"), flags{*this, parse_flag::connection_keep_alive}); - good(cn("\r\n keep-alive \t\r\n"), flags{*this, parse_flag::connection_keep_alive}); - good(cn("keep-alive \r\n \t \r\n"), flags{*this, parse_flag::connection_keep_alive}); - good(cn("keep-alive\r\n \r\n"), flags{*this, parse_flag::connection_keep_alive}); - - good(cn("upgrade\r\n"), flags{*this, parse_flag::connection_upgrade}); - good(cn("upgrade \r\n"), flags{*this, parse_flag::connection_upgrade}); - good(cn("upgrade\t \r\n"), flags{*this, parse_flag::connection_upgrade}); - good(cn("upgrade\t ,x\r\n"), flags{*this, parse_flag::connection_upgrade}); - good(cn("\r\n upgrade \t\r\n"), flags{*this, parse_flag::connection_upgrade}); - good(cn("upgrade \r\n \t \r\n"), flags{*this, parse_flag::connection_upgrade}); - good(cn("upgrade\r\n \r\n"), flags{*this, parse_flag::connection_upgrade}); - - good(cn("close,keep-alive\r\n"), flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive}); - good(cn("upgrade,keep-alive\r\n"), flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive}); - good(cn("upgrade,\r\n keep-alive\r\n"), flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive}); - good(cn("close,keep-alive,upgrade\r\n"), flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive | parse_flag::connection_upgrade}); - - good("GET / HTTP/1.1\r\n\r\n", keepalive(true)); - good("GET / HTTP/1.0\r\n\r\n", keepalive(false)); - good("GET / HTTP/1.0\r\n" - "Connection: keep-alive\r\n\r\n", keepalive(true)); - good("GET / HTTP/1.1\r\n" - "Connection: close\r\n\r\n", keepalive(false)); - - good(cn("x\r\n"), flags{*this, 0}); - good(cn("x,y\r\n"), flags{*this, 0}); - good(cn("x ,y\r\n"), flags{*this, 0}); - good(cn("x\t,y\r\n"), flags{*this, 0}); - good(cn("keep\r\n"), flags{*this, 0}); - good(cn(",keep\r\n"), flags{*this, 0}); - good(cn(" keep\r\n"), flags{*this, 0}); - good(cn("\tnone\r\n"), flags{*this, 0}); - good(cn("keep,\r\n"), flags{*this, 0}); - good(cn("keep\t\r\n"), flags{*this, 0}); - good(cn("keep\r\n"), flags{*this, 0}); - good(cn(" ,\t,,keep,, ,\t,,\r\n"), flags{*this, 0}); - good(cn("\r\n keep\r\n"), flags{*this, 0}); - good(cn("keep\r\n \r\n"), flags{*this, 0}); - good(cn("closet\r\n"), flags{*this, 0}); - good(cn(",closet\r\n"), flags{*this, 0}); - good(cn(" closet\r\n"), flags{*this, 0}); - good(cn("\tcloset\r\n"), flags{*this, 0}); - good(cn("closet,\r\n"), flags{*this, 0}); - good(cn("closet\t\r\n"), flags{*this, 0}); - good(cn("closet\r\n"), flags{*this, 0}); - good(cn(" ,\t,,closet,, ,\t,,\r\n"), flags{*this, 0}); - good(cn("\r\n closet\r\n"), flags{*this, 0}); - good(cn("closet\r\n \r\n"), flags{*this, 0}); - good(cn("clog\r\n"), flags{*this, 0}); - good(cn("key\r\n"), flags{*this, 0}); - good(cn("uptown\r\n"), flags{*this, 0}); - good(cn("keeper\r\n \r\n"), flags{*this, 0}); - good(cn("keep-alively\r\n \r\n"), flags{*this, 0}); - good(cn("up\r\n \r\n"), flags{*this, 0}); - good(cn("upgrader\r\n \r\n"), flags{*this, 0}); - good(cn("none\r\n"), flags{*this, 0}); - good(cn("\r\n none\r\n"), flags{*this, 0}); - - good(m("ConnectioX: close\r\n"), flags{*this, 0}); - good(m("Condor: close\r\n"), flags{*this, 0}); - good(m("Connect: close\r\n"), flags{*this, 0}); - good(m("Connections: close\r\n"), flags{*this, 0}); - - good(m("Proxy-Connection: close\r\n"), flags{*this, parse_flag::connection_close}); - good(m("Proxy-Connection: keep-alive\r\n"), flags{*this, parse_flag::connection_keep_alive}); - good(m("Proxy-Connection: upgrade\r\n"), flags{*this, parse_flag::connection_upgrade}); - good(m("Proxy-ConnectioX: none\r\n"), flags{*this, 0}); - good(m("Proxy-Connections: 1\r\n"), flags{*this, 0}); - good(m("Proxy-Connotes: see-also\r\n"), flags{*this, 0}); - - bad(cn("["), parse_error::bad_value); - bad(cn("\"\r\n"), parse_error::bad_value); - bad(cn("close[\r\n"), parse_error::bad_value); - bad(cn("close [\r\n"), parse_error::bad_value); - bad(cn("close, upgrade [\r\n"), parse_error::bad_value); - bad(cn("upgrade[]\r\n"), parse_error::bad_value); - bad(cn("keep\r\n -alive\r\n"), parse_error::bad_value); - bad(cn("keep-alive[\r\n"), parse_error::bad_value); - bad(cn("keep-alive []\r\n"), parse_error::bad_value); - bad(cn("no[ne]\r\n"), parse_error::bad_value); - } - - void testContentLengthHeader() - { - auto const length = - [&](std::string const& s, std::uint64_t v) - { - good(body_what::skip, s, - [&](fail_parser const& p) - { - BEAST_EXPECT(p.content_length() == v); - if(v != no_content_length) - BEAST_EXPECT(p.flags() & parse_flag::contentlength); - }); - }; - auto const c = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\nContent-Length: " + s + "\r\n"; - }; - auto const m = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\n" + s + "\r\n"; - }; - - length(c("0\r\n"), 0); - length(c("00\r\n"), 0); - length(c("1\r\n"), 1); - length(c("01\r\n"), 1); - length(c("9\r\n"), 9); - length(c("123456789\r\n"), 123456789); - length(c("42 \r\n"), 42); - length(c("42\t\r\n"), 42); - length(c("42 \t \r\n"), 42); - length(c("42\r\n \t \r\n"), 42); - - good(m("Content-LengtX: 0\r\n"), flags{*this, 0}); - good(m("Content-Lengths: many\r\n"), flags{*this, 0}); - good(m("Content: full\r\n"), flags{*this, 0}); - - bad(c("\r\n"), parse_error::bad_content_length); - bad(c("18446744073709551616\r\n"), parse_error::bad_content_length); - bad(c("0 0\r\n"), parse_error::bad_content_length); - bad(c("0 1\r\n"), parse_error::bad_content_length); - bad(c(",\r\n"), parse_error::bad_content_length); - bad(c("0,\r\n"), parse_error::bad_content_length); - bad(m( - "Content-Length: 0\r\nContent-Length: 0\r\n"), parse_error::bad_content_length); - } - - void testTransferEncodingHeader() - { - auto const m = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\n" + s + "\r\n"; - }; - auto const ce = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\nTransfer-Encoding: " + s + "\r\n0\r\n\r\n"; - }; - auto const te = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\nTransfer-Encoding: " + s + "\r\n"; - }; - good(ce("chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("chunked \r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("chunked\t\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("chunked \t\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce(" chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("\tchunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("chunked,\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("chunked ,\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("chunked, \r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce(",chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce(", chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce(" ,chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("chunked\r\n \r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("\r\n chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce(",\r\n chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("\r\n ,chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce(",\r\n chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("gzip, chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("gzip, chunked \r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("gzip, \r\n chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - - // Technically invalid but beyond the parser's scope to detect - good(ce("custom;key=\",chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - - good(te("gzip\r\n"), flags{*this, 0}); - good(te("chunked, gzip\r\n"), flags{*this, 0}); - good(te("chunked\r\n , gzip\r\n"), flags{*this, 0}); - good(te("chunked,\r\n gzip\r\n"), flags{*this, 0}); - good(te("chunked,\r\n ,gzip\r\n"), flags{*this, 0}); - good(te("bigchunked\r\n"), flags{*this, 0}); - good(te("chunk\r\n ked\r\n"), flags{*this, 0}); - good(te("bar\r\n ley chunked\r\n"), flags{*this, 0}); - good(te("barley\r\n chunked\r\n"), flags{*this, 0}); - - good(m("Transfer-EncodinX: none\r\n"), flags{*this, 0}); - good(m("Transfer-Encodings: 2\r\n"), flags{*this, 0}); - good(m("Transfer-Encoded: false\r\n"), flags{*this, 0}); - - bad(body_what::skip, - "HTTP/1.1 200 OK\r\n" - "Content-Length: 1\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n", parse_error::illegal_content_length); - } - - void testUpgradeHeader() - { - auto const m = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\n" + s + "\r\n"; - }; - good(m("Upgrade:\r\n"), flags{*this, parse_flag::upgrade}); - good(m("Upgrade: \r\n"), flags{*this, parse_flag::upgrade}); - good(m("Upgrade: yes\r\n"), flags{*this, parse_flag::upgrade}); - - good(m("Up: yes\r\n"), flags{*this, 0}); - good(m("UpgradX: none\r\n"), flags{*this, 0}); - good(m("Upgrades: 2\r\n"), flags{*this, 0}); - good(m("Upsample: 4x\r\n"), flags{*this, 0}); - - good( - "GET / HTTP/1.1\r\n" - "Connection: upgrade\r\n" - "Upgrade: WebSocket\r\n" - "\r\n", - [&](fail_parser const& p) - { - BEAST_EXPECT(p.upgrade()); - }); - } - - //-------------------------------------------------------------------------- - - class body_f - { - suite& s_; - std::string const& body_; - - public: - body_f(body_f&&) = default; - - body_f(suite& s, std::string const& v) - : s_(s) - , body_(v) - { - } - - template - void - operator()(Parser const& p) const - { - s_.BEAST_EXPECT(p.body == body_); - } - }; - - template - static - boost::asio::const_buffers_1 - buf(char const (&s)[N]) - { - return { s, N-1 }; - } - - void testBody() - { - using boost::asio::buffer; - auto const body = - [&](std::string const& s) - { - return body_f{*this, s}; - }; - good( - "GET / HTTP/1.1\r\n" - "Content-Length: 1\r\n" - "\r\n" - "1", - body("1")); - - good( - "HTTP/1.0 200 OK\r\n" - "\r\n" - "hello", - body("hello")); - - // on_body returns 2, meaning upgrade - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p{fc}; - p.on_body_rv(body_what::upgrade); - p.write(buf( - "GET / HTTP/1.1\r\n" - "Content-Length: 1\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - } - - // write the body in 3 pieces - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buffer_cat( - buf("GET / HTTP/1.1\r\n" - "Content-Length: 10\r\n" - "\r\n"), - buf("12"), - buf("345"), - buf("67890")), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - BEAST_EXPECT(! p.needs_eof()); - p.write_eof(ec); - BEAST_EXPECT(! ec); - p.write_eof(ec); - BEAST_EXPECT(! ec); - p.write(buf("GET / HTTP/1.1\r\n\r\n"), ec); - BEAST_EXPECT(ec == parse_error::connection_closed); - } - - // request without Content-Length or - // Transfer-Encoding: chunked has no body. - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buf( - "GET / HTTP/1.0\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(! p.needs_eof()); - BEAST_EXPECT(p.complete()); - } - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buf( - "GET / HTTP/1.1\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(! p.needs_eof()); - BEAST_EXPECT(p.complete()); - } - - // response without Content-Length or - // Transfer-Encoding: chunked requires eof. - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buf( - "HTTP/1.0 200 OK\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(! p.complete()); - BEAST_EXPECT(p.needs_eof()); - p.write(buf( - "hello" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(! p.complete()); - BEAST_EXPECT(p.needs_eof()); - p.write_eof(ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - p.write(buf("GET / HTTP/1.1\r\n\r\n"), ec); - BEAST_EXPECT(ec == parse_error::connection_closed); - } - - // 304 "Not Modified" response does not require eof - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buf( - "HTTP/1.0 304 Not Modified\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(! p.needs_eof()); - BEAST_EXPECT(p.complete()); - } - - // Chunked response does not require eof - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buf( - "HTTP/1.1 200 OK\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(! p.needs_eof()); - BEAST_EXPECT(! p.complete()); - p.write(buf( - "0\r\n\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(! p.needs_eof()); - BEAST_EXPECT(p.complete()); - } - - // restart: 1.0 assumes Connection: close - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buf( - "GET / HTTP/1.0\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - p.write(buf( - "GET / HTTP/1.0\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(ec == parse_error::connection_closed); - } - - // restart: 1.1 assumes Connection: keep-alive - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buf( - "GET / HTTP/1.1\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - p.write(buf( - "GET / HTTP/1.0\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - } - - bad(body_what::normal, - "GET / HTTP/1.1\r\n" - "Content-Length: 1\r\n" - "\r\n", - parse_error::short_read); - } - - void testChunkedBody() - { - auto const body = - [&](std::string const& s) - { - return body_f{*this, s}; - }; - auto const ce = - [](std::string const& s) - { - return - "GET / HTTP/1.1\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" + s; - }; - - /* - chunked-body = *chunk - last-chunk - trailer-part - CRLF - chunk = chunk-size [ chunk-ext ] CRLF - chunk-data CRLF - chunk-size = 1*HEXDIG - last-chunk = 1*("0") [ chunk-ext ] CRLF - chunk-data = 1*OCTET ; a sequence of chunk-size octets - chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) - chunk-ext-name = token - chunk-ext-val = token / quoted-string - trailer-part = *( header-field CRLF ) - */ - good(ce( - "1;xy\r\n*\r\n" "0\r\n\r\n" - ), body("*")); - - good(ce( - "1;x\r\n*\r\n" "0\r\n\r\n" - ), body("*")); - - good(ce( - "1;x;y\r\n*\r\n" "0\r\n\r\n" - ), body("*")); - - good(ce( - "1;i;j=2;k=\"3\"\r\n*\r\n" "0\r\n\r\n" - ), body("*")); - - good(ce( - "1\r\n" "a\r\n" "0\r\n" "\r\n" - ), body("a")); - - good(ce( - "2\r\n" "ab\r\n" "0\r\n" "\r\n" - ), body("ab")); - - good(ce( - "2\r\n" "ab\r\n" "1\r\n" "c\r\n" "0\r\n" "\r\n" - ), body("abc")); - - good(ce( - "10\r\n" "1234567890123456\r\n" "0\r\n" "\r\n" - ), body("1234567890123456")); - - bad(ce("ffffffffffffffff0\r\n0\r\n\r\n"), parse_error::bad_content_length); - bad(ce("g\r\n0\r\n\r\n"), parse_error::invalid_chunk_size); - bad(ce("0g\r\n0\r\n\r\n"), parse_error::invalid_chunk_size); - bad(ce("0\r_\n"), parse_error::bad_crlf); - bad(ce("1\r\n*_\r\n"), parse_error::bad_crlf); - bad(ce("1\r\n*\r_\n"), parse_error::bad_crlf); - bad(ce("1;,x\r\n*\r\n" "0\r\n\r\n"), parse_error::invalid_ext_name); - bad(ce("1;x,\r\n*\r\n" "0\r\n\r\n"), parse_error::invalid_ext_name); - } - - void testLimits() - { - std::size_t n; - static std::size_t constexpr Limit = 100; - { - for(n = 1; n < Limit; ++n) - { - test::fail_counter fc(1000); - fail_parser p(fc); - p.set_option(header_max_size{n}); - error_code ec; - p.write(buf( - "GET / HTTP/1.1\r\n" - "User-Agent: beast\r\n" - "\r\n" - ), ec); - if(! ec) - break; - BEAST_EXPECT(ec == parse_error::header_too_big); - } - BEAST_EXPECT(n < Limit); - } - { - for(n = 1; n < Limit; ++n) - { - test::fail_counter fc(1000); - fail_parser p(fc); - p.set_option(header_max_size{n}); - error_code ec; - p.write(buf( - "HTTP/1.1 200 OK\r\n" - "Server: beast\r\n" - "Content-Length: 4\r\n" - "\r\n" - "****" - ), ec); - if(! ec) - break; - BEAST_EXPECT(ec == parse_error::header_too_big); - } - BEAST_EXPECT(n < Limit); - } - { - test::fail_counter fc(1000); - fail_parser p(fc); - p.set_option(body_max_size{2}); - error_code ec; - p.write(buf( - "HTTP/1.1 200 OK\r\n" - "Server: beast\r\n" - "Content-Length: 4\r\n" - "\r\n" - "****" - ), ec); - BEAST_EXPECT(ec == parse_error::body_too_big); - } - } - - void run() override - { - testCallbacks(); - testRequestLine(); - testStatusLine(); - testHeaders(); - testConnectionHeader(); - testContentLengthHeader(); - testTransferEncodingHeader(); - testUpgradeHeader(); - testBody(); - testChunkedBody(); - testLimits(); - } -}; - -BEAST_DEFINE_TESTSUITE(basic_parser_v1,http,beast); - -} // http -} // beast diff --git a/test/core/placeholders.cpp b/test/http/buffer_body.cpp similarity index 87% rename from test/core/placeholders.cpp rename to test/http/buffer_body.cpp index 1f4955d5be..2526a3c03f 100644 --- a/test/core/placeholders.cpp +++ b/test/http/buffer_body.cpp @@ -6,4 +6,4 @@ // // Test that header file is self-contained. -#include +#include diff --git a/test/http/chunk_encode.cpp b/test/http/chunk_encode.cpp deleted file mode 100644 index 9bbe68f36d..0000000000 --- a/test/http/chunk_encode.cpp +++ /dev/null @@ -1,136 +0,0 @@ -// -// 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) -// - -// Test that header file is self-contained. -#include - -#include -#include - -namespace beast { -namespace http { - -class chunk_encode_test : public beast::unit_test::suite -{ -public: - struct final_chunk - { - std::string s; - - final_chunk() = default; - - explicit - final_chunk(std::string s_) - : s(std::move(s_)) - { - } - }; - - static - void - encode1(std::string& s, final_chunk const& fc) - { - using boost::asio::buffer; - if(! fc.s.empty()) - s.append(to_string(chunk_encode( - false, buffer(fc.s.data(), fc.s.size())))); - s.append(to_string(chunk_encode_final())); - } - - static - void - encode1(std::string& s, std::string const& piece) - { - using boost::asio::buffer; - s.append(to_string(chunk_encode( - false, buffer(piece.data(), piece.size())))); - } - - static - inline - void - encode(std::string&) - { - } - - template - static - void - encode(std::string& s, Arg const& arg, Args const&... args) - { - encode1(s, arg); - encode(s, args...); - } - - template - void - check(std::string const& answer, Args const&... args) - { - std::string s; - encode(s, args...); - BEAST_EXPECT(s == answer); - } - - void run() override - { - check( - "0\r\n\r\n" - "0\r\n\r\n", - "", final_chunk{}); - - check( - "1\r\n" - "*\r\n" - "0\r\n\r\n", - final_chunk("*")); - - check( - "2\r\n" - "**\r\n" - "0\r\n\r\n", - final_chunk("**")); - - check( - "1\r\n" - "*\r\n" - "1\r\n" - "*\r\n" - "0\r\n\r\n", - "*", final_chunk("*")); - - check( - "5\r\n" - "*****\r\n" - "7\r\n" - "*******\r\n" - "0\r\n\r\n", - "*****", final_chunk("*******")); - - check( - "1\r\n" - "*\r\n" - "1\r\n" - "*\r\n" - "0\r\n\r\n", - "*", "*", final_chunk{}); - - check( - "4\r\n" - "****\r\n" - "0\r\n\r\n", - "****", final_chunk{}); - - BEAST_EXPECT(to_string(chunk_encode(true, - boost::asio::buffer("****", 4))) == - "4\r\n****\r\n0\r\n\r\n"); - } -}; - -BEAST_DEFINE_TESTSUITE(chunk_encode,http,beast); - -} // http -} // beast diff --git a/test/http/doc_examples.cpp b/test/http/doc_examples.cpp new file mode 100644 index 0000000000..7017e176d9 --- /dev/null +++ b/test/http/doc_examples.cpp @@ -0,0 +1,303 @@ +// +// 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 "example/doc/http_examples.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +class doc_examples_test + : public beast::unit_test::suite + , public beast::test::enable_yield_to +{ +public: + // two threads, for some examples using a pipe + doc_examples_test() + : enable_yield_to(2) + { + } + + template + bool + equal_body(string_view sv, string_view body) + { + test::string_istream si{ + get_io_service(), sv.to_string()}; + message m; + multi_buffer b; + try + { + read(si, b, m); + return m.body == body; + } + catch(std::exception const& e) + { + log << "equal_body: " << e.what() << std::endl; + return false; + } + } + + void + doExpect100Continue() + { + test::pipe p{ios_}; + yield_to( + [&](yield_context) + { + error_code ec; + flat_buffer buffer; + receive_expect_100_continue( + p.server, buffer, ec); + BEAST_EXPECTS(! ec, ec.message()); + }, + [&](yield_context) + { + flat_buffer buffer; + request req; + req.version = 11; + req.method_string("POST"); + req.target("/"); + req.insert(field::user_agent, "test"); + req.body = "Hello, world!"; + req.prepare_payload(); + + error_code ec; + send_expect_100_continue( + p.client, buffer, req, ec); + BEAST_EXPECTS(! ec, ec.message()); + }); + } + + void + doCgiResponse() + { + std::string const s = "Hello, world!"; + test::pipe child{ios_}; + child.server.read_size(3); + ostream(child.server.buffer) << s; + child.client.close(); + test::pipe p{ios_}; + error_code ec; + send_cgi_response(child.server, p.client, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(equal_body(p.server.str(), s)); + } + + void + doRelay() + { + request req; + req.version = 11; + req.method_string("POST"); + req.target("/"); + req.insert(field::user_agent, "test"); + req.body = "Hello, world!"; + req.prepare_payload(); + + test::pipe downstream{ios_}; + downstream.server.read_size(3); + test::pipe upstream{ios_}; + upstream.client.write_size(3); + + error_code ec; + write(downstream.client, req); + BEAST_EXPECTS(! ec, ec.message()); + downstream.client.close(); + + flat_buffer buffer; + relay(upstream.client, downstream.server, buffer, ec, + [&](header& h, error_code& ev) + { + ev = {}; + h.erase("Content-Length"); + h.set("Transfer-Encoding", "chunked"); + }); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(equal_body( + upstream.server.str(), req.body)); + } + + void + doReadStdStream() + { + std::string const s = + "HTTP/1.0 200 OK\r\n" + "User-Agent: test\r\n" + "\r\n" + "Hello, world!"; + std::istringstream is(s); + error_code ec; + flat_buffer buffer; + response res; + read_istream(is, buffer, res, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(boost::lexical_cast< + std::string>(res) == s); + } + + void + doWriteStdStream() + { + std::ostringstream os; + request req; + req.version = 11; + req.method(verb::get); + req.target("/"); + req.insert(field::user_agent, "test"); + error_code ec; + write_ostream(os, req, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(boost::lexical_cast< + std::string>(req) == os.str()); + } + + void + doCustomParser() + { + { + string_view s{ + "POST / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "Content-Length: 13\r\n" + "\r\n" + "Hello, world!" + }; + error_code ec; + custom_parser p; + p.put(boost::asio::buffer( + s.data(), s.size()), ec); + BEAST_EXPECTS(! ec, ec.message()); + } + { + string_view s{ + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "d\r\n" + "Hello, world!" + "\r\n" + "0\r\n\r\n" + }; + error_code ec; + custom_parser p; + p.put(boost::asio::buffer( + s.data(), s.size()), ec); + BEAST_EXPECTS(! ec, ec.message()); + } + } + + void + doHEAD() + { + test::pipe p{ios_}; + yield_to( + [&](yield_context) + { + error_code ec; + flat_buffer buffer; + do_server_head(p.server, buffer, ec); + BEAST_EXPECTS(! ec, ec.message()); + }, + [&](yield_context) + { + error_code ec; + flat_buffer buffer; + auto res = do_head_request(p.client, buffer, "/", ec); + BEAST_EXPECTS(! ec, ec.message()); + }); + } + + struct handler + { + std::string body; + + template + void + operator()(request&&) + { + } + + void + operator()(request&& req) + { + body = req.body; + } + }; + + void + doDeferredBody() + { + test::pipe p{ios_}; + ostream(p.server.buffer) << + "POST / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "Content-Type: multipart/form-data\r\n" + "Content-Length: 13\r\n" + "\r\n" + "Hello, world!"; + + handler h; + flat_buffer buffer; + do_form_request(p.server, buffer, h); + BEAST_EXPECT(h.body == "Hello, world!"); + } + + //-------------------------------------------------------------------------- + + void + doIncrementalRead() + { + test::pipe c{ios_}; + std::string s(2048, '*'); + ostream(c.server.buffer) << + "HTTP/1.1 200 OK\r\n" + "Content-Length: 2048\r\n" + "Server: test\r\n" + "\r\n" << + s; + error_code ec; + flat_buffer b; + std::stringstream ss; + read_and_print_body(ss, c.server, b, ec); + if(BEAST_EXPECTS(! ec, ec.message())) + BEAST_EXPECT(ss.str() == s); + } + + //-------------------------------------------------------------------------- + + void + run() + { + doExpect100Continue(); + doCgiResponse(); + doRelay(); + doReadStdStream(); + doWriteStdStream(); + doCustomParser(); + doHEAD(); + doDeferredBody(); + doIncrementalRead(); + } +}; + +BEAST_DEFINE_TESTSUITE(doc_examples,http,beast); + +} // http +} // beast diff --git a/test/http/doc_snippets.cpp b/test/http/doc_snippets.cpp new file mode 100644 index 0000000000..1fdf10de90 --- /dev/null +++ b/test/http/doc_snippets.cpp @@ -0,0 +1,328 @@ +// +// 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 +#include +#include +#include +#include + +using namespace beast; + +//[http_snippet_1 + +#include +using namespace beast::http; + +//] + +namespace doc_http_snippets { + +void fxx() { + + boost::asio::io_service ios; + boost::asio::io_service::work work{ios}; + std::thread t{[&](){ ios.run(); }}; + boost::asio::ip::tcp::socket sock{ios}; + +{ +//[http_snippet_2 + + request req; + req.version = 11; // HTTP/1.1 + req.method(verb::get); + req.target("/index.htm"); + req.set(field::accept, "text/html"); + req.set(field::user_agent, "Beast"); + +//] +} + +{ +//[http_snippet_3 + + response res; + res.version = 11; // HTTP/1.1 + res.result(status::ok); + res.set(field::server, "Beast"); + res.body = "Hello, world!"; + res.prepare_payload(); + +//] +} + +{ +//[http_snippet_4 + + flat_buffer buffer; // (The parser is optimized for flat buffers) + request req; + read(sock, buffer, req); + +//] +} + +{ +//[http_snippet_5 + + flat_buffer buffer; + response res; + async_read(sock, buffer, res, + [&](error_code ec) + { + std::cerr << ec.message() << std::endl; + }); + +//] +} + +{ +//[http_snippet_6 + + // This buffer's max size is too small for much of anything + flat_buffer buffer{10}; + + // Try to read a request + error_code ec; + request req; + read(sock, buffer, req, ec); + if(ec == error::buffer_overflow) + std::cerr << "Buffer limit exceeded!" << std::endl; + +//] +} + +{ +//[http_snippet_7 + + response res; + res.version = 11; + res.result(status::ok); + res.set(field::server, "Beast"); + res.body = "Hello, world!"; + + error_code ec; + write(sock, res, ec); + if(ec == error::end_of_stream) + sock.close(); +//] + +//[http_snippet_8 + async_write(sock, res, + [&](error_code) + { + if(ec) + std::cerr << ec.message() << std::endl; + }); +//] +} + +{ +//[http_snippet_10 + + response res; + + response_serializer sr{res}; + +//] +} + +} // fxx() + +//[http_snippet_12 + +/** Send a message to a stream synchronously. + + @param stream The stream to write to. This type must support + the @b SyncWriteStream concept. + + @param m The message to send. The Body type must support + the @b BodyReader concept. +*/ +template< + class SyncWriteStream, + bool isRequest, class Body, class Fields> +void +send( + SyncWriteStream& stream, + message const& m) +{ + // Check the template types + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + + // Create the instance of serializer for the message + serializer sr{m}; + + // Loop until the serializer is finished + do + { + // This call guarantees it will make some + // forward progress, or otherwise return an error. + write_some(stream, sr); + } + while(! sr.is_done()); +} + +//] + +//[http_snippet_13 + +template +void +print_response(SyncReadStream& stream) +{ + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + + // Declare a parser for an HTTP response + response_parser parser; + + // Read the entire message + read(stream, parser); + + // Now print the message + std::cout << parser.get() << std::endl; +} + +//] + +#ifdef BOOST_MSVC +//[http_snippet_14 + +template +void +print_cxx14(message const& m) +{ + error_code ec; + serializer sr{m}; + do + { + sr.next(ec, + [&sr](error_code& ec, auto const& buffer) + { + ec.assign(0, ec.category()); + std::cout << buffers(buffer); + sr.consume(boost::asio::buffer_size(buffer)); + }); + } + while(! ec && ! sr.is_done()); + if(! ec) + std::cout << std::endl; + else + std::cerr << ec.message() << std::endl; +} + +//] +#endif + +//[http_snippet_15 + +template +struct lambda +{ + Serializer& sr; + + lambda(Serializer& sr_) : sr(sr_) {} + + template + void operator()(error_code& ec, ConstBufferSequence const& buffer) const + { + ec.assign(0, ec.category()); + std::cout << buffers(buffer); + sr.consume(boost::asio::buffer_size(buffer)); + } +}; + +template +void +print(message const& m) +{ + error_code ec; + serializer sr{m}; + do + { + sr.next(ec, lambda{sr}); + } + while(! ec && ! sr.is_done()); + if(! ec) + std::cout << std::endl; + else + std::cerr << ec.message() << std::endl; +} + +//] + +#if BOOST_MSVC +//[http_snippet_16 + +template +void +split_print_cxx14(message const& m) +{ + error_code ec; + serializer sr{m}; + sr.split(true); + std::cout << "Header:" << std::endl; + do + { + sr.next(ec, + [&sr](error_code& ec, auto const& buffer) + { + ec.assign(0, ec.category()); + std::cout << buffers(buffer); + sr.consume(boost::asio::buffer_size(buffer)); + }); + } + while(! sr.is_header_done()); + if(! ec && ! sr.is_done()) + { + std::cout << "Body:" << std::endl; + do + { + sr.next(ec, + [&sr](error_code& ec, auto const& buffer) + { + ec.assign(0, ec.category()); + std::cout << buffers(buffer); + sr.consume(boost::asio::buffer_size(buffer)); + }); + } + while(! ec && ! sr.is_done()); + } + if(ec) + std::cerr << ec.message() << std::endl; +} + +//] +#endif + +//[http_snippet_17 + +struct decorator +{ + std::string s; + + template + string_view + operator()(ConstBufferSequence const& buffers) + { + s = ";x=" + std::to_string(boost::asio::buffer_size(buffers)); + return s; + } + + string_view + operator()(boost::asio::null_buffers) + { + return "Result: OK\r\n"; + } +}; + +//] + +} // doc_http_snippets diff --git a/test/http/streambuf_body.cpp b/test/http/dynamic_body.cpp similarity index 60% rename from test/http/streambuf_body.cpp rename to test/http/dynamic_body.cpp index 2b5ed5f7a7..37e3fb5d92 100644 --- a/test/http/streambuf_body.cpp +++ b/test/http/dynamic_body.cpp @@ -6,11 +6,11 @@ // // Test that header file is self-contained. -#include +#include -#include +#include #include -#include +#include #include #include #include @@ -20,12 +20,13 @@ namespace beast { namespace http { -class streambuf_body_test : public beast::unit_test::suite +class dynamic_body_test : public beast::unit_test::suite { boost::asio::io_service ios_; public: - void run() override + void + run() override { std::string const s = "HTTP/1.1 200 OK\r\n" @@ -34,15 +35,17 @@ public: "\r\n" "xyz"; test::string_istream ss(ios_, s); - parser_v1 p; - streambuf sb; - parse(ss, sb, p); - BEAST_EXPECT(to_string(p.get().body.data()) == "xyz"); - BEAST_EXPECT(boost::lexical_cast(p.get()) == s); + response_parser p; + multi_buffer b; + read(ss, b, p); + auto const& m = p.get(); + BEAST_EXPECT(boost::lexical_cast( + buffers(m.body.data())) == "xyz"); + BEAST_EXPECT(boost::lexical_cast(m) == s); } }; -BEAST_DEFINE_TESTSUITE(streambuf_body,http,beast); +BEAST_DEFINE_TESTSUITE(dynamic_body,http,beast); } // http } // beast diff --git a/test/http/empty_body.cpp b/test/http/empty_body.cpp index fa190ed38e..a1fdfd5ec6 100644 --- a/test/http/empty_body.cpp +++ b/test/http/empty_body.cpp @@ -7,3 +7,13 @@ // Test that header file is self-contained. #include + +namespace beast { +namespace http { + +BOOST_STATIC_ASSERT(is_body::value); +BOOST_STATIC_ASSERT(is_body_reader::value); +BOOST_STATIC_ASSERT(is_body_writer::value); + +} // http +} // beast diff --git a/test/http/error.cpp b/test/http/error.cpp new file mode 100644 index 0000000000..52d986bb90 --- /dev/null +++ b/test/http/error.cpp @@ -0,0 +1,66 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include + +#include +#include + +namespace beast { +namespace http { + +class error_test : public unit_test::suite +{ +public: + void + check(char const* name, error ev) + { + auto const ec = make_error_code(ev); + BEAST_EXPECT(std::string(ec.category().name()) == name); + BEAST_EXPECT(! ec.message().empty()); + BEAST_EXPECT(std::addressof(ec.category()) == + std::addressof(detail::get_http_error_category())); + BEAST_EXPECT(detail::get_http_error_category().equivalent( + static_cast::type>(ev), + ec.category().default_error_condition( + static_cast::type>(ev)))); + BEAST_EXPECT(detail::get_http_error_category().equivalent( + ec, static_cast::type>(ev))); + } + + void + run() override + { + check("beast.http", error::end_of_stream); + check("beast.http", error::partial_message); + check("beast.http", error::need_more); + check("beast.http", error::unexpected_body); + check("beast.http", error::need_buffer); + check("beast.http", error::buffer_overflow); + check("beast.http", error::body_limit); + check("beast.http", error::bad_alloc); + + check("beast.http", error::bad_line_ending); + check("beast.http", error::bad_method); + check("beast.http", error::bad_target); + check("beast.http", error::bad_version); + check("beast.http", error::bad_status); + check("beast.http", error::bad_reason); + check("beast.http", error::bad_field); + check("beast.http", error::bad_value); + check("beast.http", error::bad_content_length); + check("beast.http", error::bad_transfer_encoding); + check("beast.http", error::bad_chunk); + check("beast.http", error::bad_obs_fold); + } +}; + +BEAST_DEFINE_TESTSUITE(error,http,beast); + +} // http +} // beast diff --git a/test/http/fail_parser.hpp b/test/http/fail_parser.hpp deleted file mode 100644 index 656516332d..0000000000 --- a/test/http/fail_parser.hpp +++ /dev/null @@ -1,120 +0,0 @@ -// -// 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_HTTP_TEST_FAIL_PARSER_HPP -#define BEAST_HTTP_TEST_FAIL_PARSER_HPP - -#include -#include - -namespace beast { -namespace http { - -template -class fail_parser - : public basic_parser_v1> -{ - test::fail_counter& fc_; - std::uint64_t content_length_ = no_content_length; - body_what body_rv_ = body_what::normal; - -public: - std::string body; - - template - explicit - fail_parser(test::fail_counter& fc, Args&&... args) - : fc_(fc) - { - } - - void - on_body_rv(body_what rv) - { - body_rv_ = rv; - } - - // valid on successful parse - std::uint64_t - content_length() const - { - return content_length_; - } - - void on_start(error_code& ec) - { - fc_.fail(ec); - } - - void on_method(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - void on_uri(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - void on_reason(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - void on_request(error_code& ec) - { - fc_.fail(ec); - } - - void on_response(error_code& ec) - { - fc_.fail(ec); - } - - void on_field(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - void on_value(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - void - on_header(std::uint64_t content_length, error_code& ec) - { - if(fc_.fail(ec)) - return; - } - - body_what - on_body_what(std::uint64_t content_length, error_code& ec) - { - if(fc_.fail(ec)) - return body_what::normal; - content_length_ = content_length; - return body_rv_; - } - - void on_body(boost::string_ref const& s, error_code& ec) - { - if(fc_.fail(ec)) - return; - body.append(s.data(), s.size()); - } - - void on_complete(error_code& ec) - { - fc_.fail(ec); - } -}; - -} // http -} // beast - -#endif diff --git a/test/http/field.cpp b/test/http/field.cpp new file mode 100644 index 0000000000..c44f48cc89 --- /dev/null +++ b/test/http/field.cpp @@ -0,0 +1,405 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include + +#include + +namespace beast { +namespace http { + +class field_test : public beast::unit_test::suite +{ +public: + void + testField() + { + auto const match = + [&](field f, string_view s) + { + BEAST_EXPECT(iequals(to_string(f), s)); + BEAST_EXPECT(string_to_field(s) == f); + }; + + match(field::accept, "accept"); + match(field::accept, "aCcept"); + match(field::accept, "ACCEPT"); + + + match(field::a_im, "A-IM"); + match(field::accept, "Accept"); + match(field::accept_additions, "Accept-Additions"); + match(field::accept_charset, "Accept-Charset"); + match(field::accept_datetime, "Accept-Datetime"); + match(field::accept_encoding, "Accept-Encoding"); + match(field::accept_features, "Accept-Features"); + match(field::accept_language, "Accept-Language"); + match(field::accept_patch, "Accept-Patch"); + match(field::accept_post, "Accept-Post"); + match(field::accept_ranges, "Accept-Ranges"); + match(field::access_control, "Access-Control"); + match(field::access_control_allow_credentials, "Access-Control-Allow-Credentials"); + match(field::access_control_allow_headers, "Access-Control-Allow-Headers"); + match(field::access_control_allow_methods, "Access-Control-Allow-Methods"); + match(field::access_control_allow_origin, "Access-Control-Allow-Origin"); + match(field::access_control_max_age, "Access-Control-Max-Age"); + match(field::access_control_request_headers, "Access-Control-Request-Headers"); + match(field::access_control_request_method, "Access-Control-Request-Method"); + match(field::age, "Age"); + match(field::allow, "Allow"); + match(field::alpn, "ALPN"); + match(field::also_control, "Also-Control"); + match(field::alt_svc, "Alt-Svc"); + match(field::alt_used, "Alt-Used"); + match(field::alternate_recipient, "Alternate-Recipient"); + match(field::alternates, "Alternates"); + match(field::apparently_to, "Apparently-To"); + match(field::apply_to_redirect_ref, "Apply-To-Redirect-Ref"); + match(field::approved, "Approved"); + match(field::archive, "Archive"); + match(field::archived_at, "Archived-At"); + match(field::article_names, "Article-Names"); + match(field::article_updates, "Article-Updates"); + match(field::authentication_control, "Authentication-Control"); + match(field::authentication_info, "Authentication-Info"); + match(field::authentication_results, "Authentication-Results"); + match(field::authorization, "Authorization"); + match(field::auto_submitted, "Auto-Submitted"); + match(field::autoforwarded, "Autoforwarded"); + match(field::autosubmitted, "Autosubmitted"); + match(field::base, "Base"); + match(field::bcc, "Bcc"); + match(field::body, "Body"); + match(field::c_ext, "C-Ext"); + match(field::c_man, "C-Man"); + match(field::c_opt, "C-Opt"); + match(field::c_pep, "C-PEP"); + match(field::c_pep_info, "C-PEP-Info"); + match(field::cache_control, "Cache-Control"); + match(field::caldav_timezones, "CalDAV-Timezones"); + match(field::cancel_key, "Cancel-Key"); + match(field::cancel_lock, "Cancel-Lock"); + match(field::cc, "Cc"); + match(field::close, "Close"); + match(field::comments, "Comments"); + match(field::compliance, "Compliance"); + match(field::connection, "Connection"); + match(field::content_alternative, "Content-Alternative"); + match(field::content_base, "Content-Base"); + match(field::content_description, "Content-Description"); + match(field::content_disposition, "Content-Disposition"); + match(field::content_duration, "Content-Duration"); + match(field::content_encoding, "Content-Encoding"); + match(field::content_features, "Content-features"); + match(field::content_id, "Content-ID"); + match(field::content_identifier, "Content-Identifier"); + match(field::content_language, "Content-Language"); + match(field::content_length, "Content-Length"); + match(field::content_location, "Content-Location"); + match(field::content_md5, "Content-MD5"); + match(field::content_range, "Content-Range"); + match(field::content_return, "Content-Return"); + match(field::content_script_type, "Content-Script-Type"); + match(field::content_style_type, "Content-Style-Type"); + match(field::content_transfer_encoding, "Content-Transfer-Encoding"); + match(field::content_type, "Content-Type"); + match(field::content_version, "Content-Version"); + match(field::control, "Control"); + match(field::conversion, "Conversion"); + match(field::conversion_with_loss, "Conversion-With-Loss"); + match(field::cookie, "Cookie"); + match(field::cookie2, "Cookie2"); + match(field::cost, "Cost"); + match(field::dasl, "DASL"); + match(field::date, "Date"); + match(field::date_received, "Date-Received"); + match(field::dav, "DAV"); + match(field::default_style, "Default-Style"); + match(field::deferred_delivery, "Deferred-Delivery"); + match(field::delivery_date, "Delivery-Date"); + match(field::delta_base, "Delta-Base"); + match(field::depth, "Depth"); + match(field::derived_from, "Derived-From"); + match(field::destination, "Destination"); + match(field::differential_id, "Differential-ID"); + match(field::digest, "Digest"); + match(field::discarded_x400_ipms_extensions, "Discarded-X400-IPMS-Extensions"); + match(field::discarded_x400_mts_extensions, "Discarded-X400-MTS-Extensions"); + match(field::disclose_recipients, "Disclose-Recipients"); + match(field::disposition_notification_options, "Disposition-Notification-Options"); + match(field::disposition_notification_to, "Disposition-Notification-To"); + match(field::distribution, "Distribution"); + match(field::dkim_signature, "DKIM-Signature"); + match(field::dl_expansion_history, "DL-Expansion-History"); + match(field::downgraded_bcc, "Downgraded-Bcc"); + match(field::downgraded_cc, "Downgraded-Cc"); + match(field::downgraded_disposition_notification_to, "Downgraded-Disposition-Notification-To"); + match(field::downgraded_final_recipient, "Downgraded-Final-Recipient"); + match(field::downgraded_from, "Downgraded-From"); + match(field::downgraded_in_reply_to, "Downgraded-In-Reply-To"); + match(field::downgraded_mail_from, "Downgraded-Mail-From"); + match(field::downgraded_message_id, "Downgraded-Message-Id"); + match(field::downgraded_original_recipient, "Downgraded-Original-Recipient"); + match(field::downgraded_rcpt_to, "Downgraded-Rcpt-To"); + match(field::downgraded_references, "Downgraded-References"); + match(field::downgraded_reply_to, "Downgraded-Reply-To"); + match(field::downgraded_resent_bcc, "Downgraded-Resent-Bcc"); + match(field::downgraded_resent_cc, "Downgraded-Resent-Cc"); + match(field::downgraded_resent_from, "Downgraded-Resent-From"); + match(field::downgraded_resent_reply_to, "Downgraded-Resent-Reply-To"); + match(field::downgraded_resent_sender, "Downgraded-Resent-Sender"); + match(field::downgraded_resent_to, "Downgraded-Resent-To"); + match(field::downgraded_return_path, "Downgraded-Return-Path"); + match(field::downgraded_sender, "Downgraded-Sender"); + match(field::downgraded_to, "Downgraded-To"); + match(field::ediint_features, "EDIINT-Features"); + match(field::eesst_version, "Eesst-Version"); + match(field::encoding, "Encoding"); + match(field::encrypted, "Encrypted"); + match(field::errors_to, "Errors-To"); + match(field::etag, "ETag"); + match(field::expect, "Expect"); + match(field::expires, "Expires"); + match(field::expiry_date, "Expiry-Date"); + match(field::ext, "Ext"); + match(field::followup_to, "Followup-To"); + match(field::forwarded, "Forwarded"); + match(field::from, "From"); + match(field::generate_delivery_report, "Generate-Delivery-Report"); + match(field::getprofile, "GetProfile"); + match(field::hobareg, "Hobareg"); + match(field::host, "Host"); + match(field::http2_settings, "HTTP2-Settings"); + match(field::if_, "If"); + match(field::if_match, "If-Match"); + match(field::if_modified_since, "If-Modified-Since"); + match(field::if_none_match, "If-None-Match"); + match(field::if_range, "If-Range"); + match(field::if_schedule_tag_match, "If-Schedule-Tag-Match"); + match(field::if_unmodified_since, "If-Unmodified-Since"); + match(field::im, "IM"); + match(field::importance, "Importance"); + match(field::in_reply_to, "In-Reply-To"); + match(field::incomplete_copy, "Incomplete-Copy"); + match(field::injection_date, "Injection-Date"); + match(field::injection_info, "Injection-Info"); + match(field::jabber_id, "Jabber-ID"); + match(field::keep_alive, "Keep-Alive"); + match(field::keywords, "Keywords"); + match(field::label, "Label"); + match(field::language, "Language"); + match(field::last_modified, "Last-Modified"); + match(field::latest_delivery_time, "Latest-Delivery-Time"); + match(field::lines, "Lines"); + match(field::link, "Link"); + match(field::list_archive, "List-Archive"); + match(field::list_help, "List-Help"); + match(field::list_id, "List-ID"); + match(field::list_owner, "List-Owner"); + match(field::list_post, "List-Post"); + match(field::list_subscribe, "List-Subscribe"); + match(field::list_unsubscribe, "List-Unsubscribe"); + match(field::list_unsubscribe_post, "List-Unsubscribe-Post"); + match(field::location, "Location"); + match(field::lock_token, "Lock-Token"); + match(field::man, "Man"); + match(field::max_forwards, "Max-Forwards"); + match(field::memento_datetime, "Memento-Datetime"); + match(field::message_context, "Message-Context"); + match(field::message_id, "Message-ID"); + match(field::message_type, "Message-Type"); + match(field::meter, "Meter"); + match(field::method_check, "Method-Check"); + match(field::method_check_expires, "Method-Check-Expires"); + match(field::mime_version, "MIME-Version"); + match(field::mmhs_acp127_message_identifier, "MMHS-Acp127-Message-Identifier"); + match(field::mmhs_authorizing_users, "MMHS-Authorizing-Users"); + match(field::mmhs_codress_message_indicator, "MMHS-Codress-Message-Indicator"); + match(field::mmhs_copy_precedence, "MMHS-Copy-Precedence"); + match(field::mmhs_exempted_address, "MMHS-Exempted-Address"); + match(field::mmhs_extended_authorisation_info, "MMHS-Extended-Authorisation-Info"); + match(field::mmhs_handling_instructions, "MMHS-Handling-Instructions"); + match(field::mmhs_message_instructions, "MMHS-Message-Instructions"); + match(field::mmhs_message_type, "MMHS-Message-Type"); + match(field::mmhs_originator_plad, "MMHS-Originator-PLAD"); + match(field::mmhs_originator_reference, "MMHS-Originator-Reference"); + match(field::mmhs_other_recipients_indicator_cc, "MMHS-Other-Recipients-Indicator-CC"); + match(field::mmhs_other_recipients_indicator_to, "MMHS-Other-Recipients-Indicator-To"); + match(field::mmhs_primary_precedence, "MMHS-Primary-Precedence"); + match(field::mmhs_subject_indicator_codes, "MMHS-Subject-Indicator-Codes"); + match(field::mt_priority, "MT-Priority"); + match(field::negotiate, "Negotiate"); + match(field::newsgroups, "Newsgroups"); + match(field::nntp_posting_date, "NNTP-Posting-Date"); + match(field::nntp_posting_host, "NNTP-Posting-Host"); + match(field::non_compliance, "Non-Compliance"); + match(field::obsoletes, "Obsoletes"); + match(field::opt, "Opt"); + match(field::optional, "Optional"); + match(field::optional_www_authenticate, "Optional-WWW-Authenticate"); + match(field::ordering_type, "Ordering-Type"); + match(field::organization, "Organization"); + match(field::origin, "Origin"); + match(field::original_encoded_information_types, "Original-Encoded-Information-Types"); + match(field::original_from, "Original-From"); + match(field::original_message_id, "Original-Message-ID"); + match(field::original_recipient, "Original-Recipient"); + match(field::original_sender, "Original-Sender"); + match(field::original_subject, "Original-Subject"); + match(field::originator_return_address, "Originator-Return-Address"); + match(field::overwrite, "Overwrite"); + match(field::p3p, "P3P"); + match(field::path, "Path"); + match(field::pep, "PEP"); + match(field::pep_info, "Pep-Info"); + match(field::pics_label, "PICS-Label"); + match(field::position, "Position"); + match(field::posting_version, "Posting-Version"); + match(field::pragma, "Pragma"); + match(field::prefer, "Prefer"); + match(field::preference_applied, "Preference-Applied"); + match(field::prevent_nondelivery_report, "Prevent-NonDelivery-Report"); + match(field::priority, "Priority"); + match(field::privicon, "Privicon"); + match(field::profileobject, "ProfileObject"); + match(field::protocol, "Protocol"); + match(field::protocol_info, "Protocol-Info"); + match(field::protocol_query, "Protocol-Query"); + match(field::protocol_request, "Protocol-Request"); + match(field::proxy_authenticate, "Proxy-Authenticate"); + match(field::proxy_authentication_info, "Proxy-Authentication-Info"); + match(field::proxy_authorization, "Proxy-Authorization"); + match(field::proxy_connection, "Proxy-Connection"); + match(field::proxy_features, "Proxy-Features"); + match(field::proxy_instruction, "Proxy-Instruction"); + match(field::public_, "Public"); + match(field::public_key_pins, "Public-Key-Pins"); + match(field::public_key_pins_report_only, "Public-Key-Pins-Report-Only"); + match(field::range, "Range"); + match(field::received, "Received"); + match(field::received_spf, "Received-SPF"); + match(field::redirect_ref, "Redirect-Ref"); + match(field::references, "References"); + match(field::referer, "Referer"); + match(field::referer_root, "Referer-Root"); + match(field::relay_version, "Relay-Version"); + match(field::reply_by, "Reply-By"); + match(field::reply_to, "Reply-To"); + match(field::require_recipient_valid_since, "Require-Recipient-Valid-Since"); + match(field::resent_bcc, "Resent-Bcc"); + match(field::resent_cc, "Resent-Cc"); + match(field::resent_date, "Resent-Date"); + match(field::resent_from, "Resent-From"); + match(field::resent_message_id, "Resent-Message-ID"); + match(field::resent_reply_to, "Resent-Reply-To"); + match(field::resent_sender, "Resent-Sender"); + match(field::resent_to, "Resent-To"); + match(field::resolution_hint, "Resolution-Hint"); + match(field::resolver_location, "Resolver-Location"); + match(field::retry_after, "Retry-After"); + match(field::return_path, "Return-Path"); + match(field::safe, "Safe"); + match(field::schedule_reply, "Schedule-Reply"); + match(field::schedule_tag, "Schedule-Tag"); + match(field::sec_websocket_accept, "Sec-WebSocket-Accept"); + match(field::sec_websocket_extensions, "Sec-WebSocket-Extensions"); + match(field::sec_websocket_key, "Sec-WebSocket-Key"); + match(field::sec_websocket_protocol, "Sec-WebSocket-Protocol"); + match(field::sec_websocket_version, "Sec-WebSocket-Version"); + match(field::security_scheme, "Security-Scheme"); + match(field::see_also, "See-Also"); + match(field::sender, "Sender"); + match(field::sensitivity, "Sensitivity"); + match(field::server, "Server"); + match(field::set_cookie, "Set-Cookie"); + match(field::set_cookie2, "Set-Cookie2"); + match(field::setprofile, "SetProfile"); + match(field::sio_label, "SIO-Label"); + match(field::sio_label_history, "SIO-Label-History"); + match(field::slug, "SLUG"); + match(field::soapaction, "SoapAction"); + match(field::solicitation, "Solicitation"); + match(field::status_uri, "Status-URI"); + match(field::strict_transport_security, "Strict-Transport-Security"); + match(field::subject, "Subject"); + match(field::subok, "SubOK"); + match(field::subst, "Subst"); + match(field::summary, "Summary"); + match(field::supersedes, "Supersedes"); + match(field::surrogate_capability, "Surrogate-Capability"); + match(field::surrogate_control, "Surrogate-Control"); + match(field::tcn, "TCN"); + match(field::te, "TE"); + match(field::timeout, "Timeout"); + match(field::title, "Title"); + match(field::to, "To"); + match(field::topic, "Topic"); + match(field::trailer, "Trailer"); + match(field::transfer_encoding, "Transfer-Encoding"); + match(field::ttl, "TTL"); + match(field::ua_color, "UA-Color"); + match(field::ua_media, "UA-Media"); + match(field::ua_pixels, "UA-Pixels"); + match(field::ua_resolution, "UA-Resolution"); + match(field::ua_windowpixels, "UA-Windowpixels"); + match(field::upgrade, "Upgrade"); + match(field::urgency, "Urgency"); + match(field::uri, "URI"); + match(field::user_agent, "User-Agent"); + match(field::variant_vary, "Variant-Vary"); + match(field::vary, "Vary"); + match(field::vbr_info, "VBR-Info"); + match(field::version, "Version"); + match(field::via, "Via"); + match(field::want_digest, "Want-Digest"); + match(field::warning, "Warning"); + match(field::www_authenticate, "WWW-Authenticate"); + match(field::x_archived_at, "X-Archived-At"); + match(field::x_device_accept, "X-Device-Accept"); + match(field::x_device_accept_charset, "X-Device-Accept-Charset"); + match(field::x_device_accept_encoding, "X-Device-Accept-Encoding"); + match(field::x_device_accept_language, "X-Device-Accept-Language"); + match(field::x_device_user_agent, "X-Device-User-Agent"); + match(field::x_frame_options, "X-Frame-Options"); + match(field::x_mittente, "X-Mittente"); + match(field::x_pgp_sig, "X-PGP-Sig"); + match(field::x_ricevuta, "X-Ricevuta"); + match(field::x_riferimento_message_id, "X-Riferimento-Message-ID"); + match(field::x_tiporicevuta, "X-TipoRicevuta"); + match(field::x_trasporto, "X-Trasporto"); + match(field::x_verificasicurezza, "X-VerificaSicurezza"); + match(field::x400_content_identifier, "X400-Content-Identifier"); + match(field::x400_content_return, "X400-Content-Return"); + match(field::x400_content_type, "X400-Content-Type"); + match(field::x400_mts_identifier, "X400-MTS-Identifier"); + match(field::x400_originator, "X400-Originator"); + match(field::x400_received, "X400-Received"); + match(field::x400_recipients, "X400-Recipients"); + match(field::x400_trace, "X400-Trace"); + match(field::xref, "Xref"); + + auto const unknown = + [&](string_view s) + { + BEAST_EXPECT(string_to_field(s) == field::unknown); + }; + unknown(""); + unknown("x"); + } + + void run() override + { + testField(); + pass(); + } +}; + +BEAST_DEFINE_TESTSUITE(field,http,beast); + +} // http +} // beast diff --git a/test/http/fields.cpp b/test/http/fields.cpp index 8ac52d5bf2..0958952de9 100644 --- a/test/http/fields.cpp +++ b/test/http/fields.cpp @@ -7,3 +7,915 @@ // Test that header file is self-contained. #include + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +BOOST_STATIC_ASSERT(is_fields::value); + +class fields_test : public beast::unit_test::suite +{ +public: + template + using fa_t = basic_fields; + + using f_t = fa_t>; + + template + static + void + fill(std::size_t n, basic_fields& f) + { + for(std::size_t i = 1; i<= n; ++i) + f.insert(boost::lexical_cast(i), i); + } + + template + static + void + self_assign(U& u, V&& v) + { + u = std::forward(v); + } + + template + static + bool + empty(basic_fields const& f) + { + return f.begin() == f.end(); + } + + template + static + std::size_t + size(basic_fields const& f) + { + return std::distance(f.begin(), f.end()); + } + + void + testMembers() + { + using namespace test; + + // compare equal + using equal_t = test::test_allocator; + + // compare not equal + using unequal_t = test::test_allocator; + + // construction + { + { + fields f; + BEAST_EXPECT(f.begin() == f.end()); + } + { + unequal_t a1; + basic_fields f{a1}; + BEAST_EXPECT(f.get_allocator() == a1); + BEAST_EXPECT(f.get_allocator() != unequal_t{}); + } + } + + // move construction + { + { + basic_fields f1; + BEAST_EXPECT(f1.get_allocator()->nmove == 0); + f1.insert("1", "1"); + BEAST_EXPECT(f1["1"] == "1"); + basic_fields f2{std::move(f1)}; + BEAST_EXPECT(f2.get_allocator()->nmove == 1); + BEAST_EXPECT(f2["1"] == "1"); + BEAST_EXPECT(f1["1"] == ""); + } + // allocators equal + { + basic_fields f1; + f1.insert("1", "1"); + equal_t a; + basic_fields f2{std::move(f1), a}; + BEAST_EXPECT(f2["1"] == "1"); + BEAST_EXPECT(f1["1"] == ""); + } + { + // allocators unequal + basic_fields f1; + f1.insert("1", "1"); + unequal_t a; + basic_fields f2{std::move(f1), a}; + BEAST_EXPECT(f2["1"] == "1"); + } + } + + // copy construction + { + { + basic_fields f1; + f1.insert("1", "1"); + basic_fields f2{f1}; + BEAST_EXPECT(f1.get_allocator() == f2.get_allocator()); + BEAST_EXPECT(f1["1"] == "1"); + BEAST_EXPECT(f2["1"] == "1"); + } + { + basic_fields f1; + f1.insert("1", "1"); + unequal_t a; + basic_fields f2(f1, a); + BEAST_EXPECT(f1.get_allocator() != f2.get_allocator()); + BEAST_EXPECT(f1["1"] == "1"); + BEAST_EXPECT(f2["1"] == "1"); + } + { + basic_fields f1; + f1.insert("1", "1"); + basic_fields f2(f1); + BEAST_EXPECT(f1["1"] == "1"); + BEAST_EXPECT(f2["1"] == "1"); + } + { + basic_fields f1; + f1.insert("1", "1"); + equal_t a; + basic_fields f2(f1, a); + BEAST_EXPECT(f2.get_allocator() == a); + BEAST_EXPECT(f1["1"] == "1"); + BEAST_EXPECT(f2["1"] == "1"); + } + } + + // move assignment + { + { + fields f1; + f1.insert("1", "1"); + fields f2; + f2 = std::move(f1); + BEAST_EXPECT(f1.begin() == f1.end()); + BEAST_EXPECT(f2["1"] == "1"); + } + { + // propagate_on_container_move_assignment : true + using pocma_t = test::test_allocator; + basic_fields f1; + f1.insert("1", "1"); + basic_fields f2; + f2 = std::move(f1); + BEAST_EXPECT(f1.begin() == f1.end()); + BEAST_EXPECT(f2["1"] == "1"); + } + { + // propagate_on_container_move_assignment : false + using pocma_t = test::test_allocator; + basic_fields f1; + f1.insert("1", "1"); + basic_fields f2; + f2 = std::move(f1); + BEAST_EXPECT(f1.begin() == f1.end()); + BEAST_EXPECT(f2["1"] == "1"); + } + } + + // copy assignment + { + { + fields f1; + f1.insert("1", "1"); + fields f2; + f2 = f1; + BEAST_EXPECT(f1["1"] == "1"); + BEAST_EXPECT(f2["1"] == "1"); + basic_fields f3; + f3 = f2; + BEAST_EXPECT(f3["1"] == "1"); + } + { + // propagate_on_container_copy_assignment : true + using pocca_t = test::test_allocator; + basic_fields f1; + f1.insert("1", "1"); + basic_fields f2; + f2 = f1; + BEAST_EXPECT(f2["1"] == "1"); + } + { + // propagate_on_container_copy_assignment : false + using pocca_t = test::test_allocator; + basic_fields f1; + f1.insert("1", "1"); + basic_fields f2; + f2 = f1; + BEAST_EXPECT(f2["1"] == "1"); + } + } + + // swap + { + { + // propagate_on_container_swap : true + using pocs_t = test::test_allocator; + pocs_t a1, a2; + BEAST_EXPECT(a1 != a2); + basic_fields f1{a1}; + f1.insert("1", "1"); + basic_fields f2{a2}; + BEAST_EXPECT(f1.get_allocator() == a1); + BEAST_EXPECT(f2.get_allocator() == a2); + swap(f1, f2); + BEAST_EXPECT(f1.get_allocator() == a2); + BEAST_EXPECT(f2.get_allocator() == a1); + BEAST_EXPECT(f1.begin() == f1.end()); + BEAST_EXPECT(f2["1"] == "1"); + swap(f1, f2); + BEAST_EXPECT(f1.get_allocator() == a1); + BEAST_EXPECT(f2.get_allocator() == a2); + BEAST_EXPECT(f1["1"] == "1"); + BEAST_EXPECT(f2.begin() == f2.end()); + } + { + // propagate_on_container_swap : false + using pocs_t = test::test_allocator; + pocs_t a1, a2; + BEAST_EXPECT(a1 == a2); + BEAST_EXPECT(a1.id() != a2.id()); + basic_fields f1{a1}; + f1.insert("1", "1"); + basic_fields f2{a2}; + BEAST_EXPECT(f1.get_allocator() == a1); + BEAST_EXPECT(f2.get_allocator() == a2); + swap(f1, f2); + BEAST_EXPECT(f1.get_allocator().id() == a1.id()); + BEAST_EXPECT(f2.get_allocator().id() == a2.id()); + BEAST_EXPECT(f1.begin() == f1.end()); + BEAST_EXPECT(f2["1"] == "1"); + swap(f1, f2); + BEAST_EXPECT(f1.get_allocator().id() == a1.id()); + BEAST_EXPECT(f2.get_allocator().id() == a2.id()); + BEAST_EXPECT(f1["1"] == "1"); + BEAST_EXPECT(f2.begin() == f2.end()); + } + } + + // operations + { + fields f; + f.insert(field::user_agent, "x"); + BEAST_EXPECT(f.count(field::user_agent)); + BEAST_EXPECT(f.count(to_string(field::user_agent))); + BEAST_EXPECT(f.count(field::user_agent) == 1); + BEAST_EXPECT(f.count(to_string(field::user_agent)) == 1); + f.insert(field::user_agent, "y"); + BEAST_EXPECT(f.count(field::user_agent) == 2); + } + } + + void testHeaders() + { + f_t f1; + BEAST_EXPECT(empty(f1)); + fill(1, f1); + BEAST_EXPECT(size(f1) == 1); + f_t f2; + f2 = f1; + BEAST_EXPECT(size(f2) == 1); + f2.insert("2", "2"); + BEAST_EXPECT(std::distance(f2.begin(), f2.end()) == 2); + f1 = std::move(f2); + BEAST_EXPECT(size(f1) == 2); + BEAST_EXPECT(size(f2) == 0); + f_t f3(std::move(f1)); + BEAST_EXPECT(size(f3) == 2); + BEAST_EXPECT(size(f1) == 0); + self_assign(f3, std::move(f3)); + BEAST_EXPECT(size(f3) == 2); + BEAST_EXPECT(f2.erase("Not-Present") == 0); + } + + void testRFC2616() + { + f_t f; + f.insert("a", "w"); + f.insert("a", "x"); + f.insert("aa", "y"); + f.insert("f", "z"); + BEAST_EXPECT(f.count("a") == 2); + } + + void testErase() + { + f_t f; + f.insert("a", "w"); + f.insert("a", "x"); + f.insert("aa", "y"); + f.insert("f", "z"); + BEAST_EXPECT(size(f) == 4); + f.erase("a"); + BEAST_EXPECT(size(f) == 2); + } + + void + testContainer() + { + { + // group fields + fields f; + f.insert(field::age, 1); + f.insert(field::body, 2); + f.insert(field::close, 3); + f.insert(field::body, 4); + BEAST_EXPECT(std::next(f.begin(), 0)->name() == field::age); + BEAST_EXPECT(std::next(f.begin(), 1)->name() == field::body); + BEAST_EXPECT(std::next(f.begin(), 2)->name() == field::body); + BEAST_EXPECT(std::next(f.begin(), 3)->name() == field::close); + BEAST_EXPECT(std::next(f.begin(), 0)->name_string() == "Age"); + BEAST_EXPECT(std::next(f.begin(), 1)->name_string() == "Body"); + BEAST_EXPECT(std::next(f.begin(), 2)->name_string() == "Body"); + BEAST_EXPECT(std::next(f.begin(), 3)->name_string() == "Close"); + BEAST_EXPECT(std::next(f.begin(), 0)->value() == "1"); + BEAST_EXPECT(std::next(f.begin(), 1)->value() == "2"); + BEAST_EXPECT(std::next(f.begin(), 2)->value() == "4"); + BEAST_EXPECT(std::next(f.begin(), 3)->value() == "3"); + BEAST_EXPECT(f.erase(field::body) == 2); + BEAST_EXPECT(std::next(f.begin(), 0)->name_string() == "Age"); + BEAST_EXPECT(std::next(f.begin(), 1)->name_string() == "Close"); + } + { + // group fields, case insensitive + fields f; + f.insert("a", 1); + f.insert("ab", 2); + f.insert("b", 3); + f.insert("AB", 4); + BEAST_EXPECT(std::next(f.begin(), 0)->name() == field::unknown); + BEAST_EXPECT(std::next(f.begin(), 1)->name() == field::unknown); + BEAST_EXPECT(std::next(f.begin(), 2)->name() == field::unknown); + BEAST_EXPECT(std::next(f.begin(), 3)->name() == field::unknown); + BEAST_EXPECT(std::next(f.begin(), 0)->name_string() == "a"); + BEAST_EXPECT(std::next(f.begin(), 1)->name_string() == "ab"); + BEAST_EXPECT(std::next(f.begin(), 2)->name_string() == "AB"); + BEAST_EXPECT(std::next(f.begin(), 3)->name_string() == "b"); + BEAST_EXPECT(std::next(f.begin(), 0)->value() == "1"); + BEAST_EXPECT(std::next(f.begin(), 1)->value() == "2"); + BEAST_EXPECT(std::next(f.begin(), 2)->value() == "4"); + BEAST_EXPECT(std::next(f.begin(), 3)->value() == "3"); + BEAST_EXPECT(f.erase("Ab") == 2); + BEAST_EXPECT(std::next(f.begin(), 0)->name_string() == "a"); + BEAST_EXPECT(std::next(f.begin(), 1)->name_string() == "b"); + } + { + // verify insertion orde + fields f; + f.insert( "a", 1); + f.insert("dd", 2); + f.insert("b", 3); + f.insert("dD", 4); + f.insert("c", 5); + f.insert("Dd", 6); + f.insert("DD", 7); + f.insert( "e", 8); + BEAST_EXPECT(f.count("dd") == 4); + BEAST_EXPECT(std::next(f.begin(), 1)->name_string() == "dd"); + BEAST_EXPECT(std::next(f.begin(), 2)->name_string() == "dD"); + BEAST_EXPECT(std::next(f.begin(), 3)->name_string() == "Dd"); + BEAST_EXPECT(std::next(f.begin(), 4)->name_string() == "DD"); + f.set("dd", "-"); + BEAST_EXPECT(f.count("dd") == 1); + BEAST_EXPECT(f["dd"] == "-"); + } + } + + struct sized_body + { + using value_type = std::uint64_t; + + static + std::uint64_t + size(value_type const& v) + { + return v; + } + }; + + struct unsized_body + { + struct value_type {}; + }; + + void + testPreparePayload() + { + // GET, empty + { + request req; + req.version = 11; + req.method(verb::get); + + req.prepare_payload(); + BEAST_EXPECT(req.count(field::content_length) == 0); + BEAST_EXPECT(req.count(field::transfer_encoding) == 0); + + req.set(field::content_length, "0"); + req.set(field::transfer_encoding, "chunked"); + req.prepare_payload(); + + BEAST_EXPECT(req.count(field::content_length) == 0); + BEAST_EXPECT(req.count(field::transfer_encoding) == 0); + + req.set(field::transfer_encoding, "deflate"); + req.prepare_payload(); + BEAST_EXPECT(req.count(field::content_length) == 0); + BEAST_EXPECT(req[field::transfer_encoding] == "deflate"); + + req.set(field::transfer_encoding, "deflate, chunked"); + req.prepare_payload(); + BEAST_EXPECT(req.count(field::content_length) == 0); + BEAST_EXPECT(req[field::transfer_encoding] == "deflate"); + } + + // GET, sized + { + request req; + req.version = 11; + req.method(verb::get); + req.body = 50; + + req.prepare_payload(); + BEAST_EXPECT(req[field::content_length] == "50"); + BEAST_EXPECT(req[field::transfer_encoding] == ""); + + req.set(field::content_length, "0"); + req.set(field::transfer_encoding, "chunked"); + req.prepare_payload(); + BEAST_EXPECT(req[field::content_length] == "50"); + BEAST_EXPECT(req.count(field::transfer_encoding) == 0); + + req.set(field::transfer_encoding, "deflate, chunked"); + req.prepare_payload(); + BEAST_EXPECT(req[field::content_length] == "50"); + BEAST_EXPECT(req[field::transfer_encoding] == "deflate"); + } + + // PUT, empty + { + request req; + req.version = 11; + req.method(verb::put); + + req.prepare_payload(); + BEAST_EXPECT(req[field::content_length] == "0"); + BEAST_EXPECT(req.count(field::transfer_encoding) == 0); + + req.set(field::content_length, "50"); + req.set(field::transfer_encoding, "deflate, chunked"); + req.prepare_payload(); + BEAST_EXPECT(req[field::content_length] == "0"); + BEAST_EXPECT(req[field::transfer_encoding] == "deflate"); + } + + // PUT, sized + { + request req; + req.version = 11; + req.method(verb::put); + req.body = 50; + + req.prepare_payload(); + BEAST_EXPECT(req[field::content_length] == "50"); + BEAST_EXPECT(req.count(field::transfer_encoding) == 0); + + req.set(field::content_length, "25"); + req.set(field::transfer_encoding, "deflate, chunked"); + req.prepare_payload(); + BEAST_EXPECT(req[field::content_length] == "50"); + BEAST_EXPECT(req[field::transfer_encoding] == "deflate"); + } + + // POST, unsized + { + request req; + req.version = 11; + req.method(verb::post); + + req.prepare_payload(); + BEAST_EXPECT(req.count(field::content_length) == 0); + BEAST_EXPECT(req[field::transfer_encoding] == "chunked"); + + req.set(field::transfer_encoding, "deflate"); + req.prepare_payload(); + BEAST_EXPECT(req.count(field::content_length) == 0); + BEAST_EXPECT(req[field::transfer_encoding] == "deflate, chunked"); + } + + // POST, unsized HTTP/1.0 + { + request req; + req.version = 10; + req.method(verb::post); + + req.prepare_payload(); + BEAST_EXPECT(req.count(field::content_length) == 0); + BEAST_EXPECT(req.count(field::transfer_encoding) == 0); + + req.set(field::transfer_encoding, "deflate"); + req.prepare_payload(); + BEAST_EXPECT(req.count(field::content_length) == 0); + BEAST_EXPECT(req[field::transfer_encoding] == "deflate"); + } + + // OK, empty + { + response res; + res.version = 11; + + res.prepare_payload(); + BEAST_EXPECT(res[field::content_length] == "0"); + BEAST_EXPECT(res.count(field::transfer_encoding) == 0); + + res.erase(field::content_length); + res.set(field::transfer_encoding, "chunked"); + res.prepare_payload(); + BEAST_EXPECT(res[field::content_length] == "0"); + BEAST_EXPECT(res.count(field::transfer_encoding) == 0); + } + + // OK, sized + { + response res; + res.version = 11; + res.body = 50; + + res.prepare_payload(); + BEAST_EXPECT(res[field::content_length] == "50"); + BEAST_EXPECT(res.count(field::transfer_encoding) == 0); + + res.erase(field::content_length); + res.set(field::transfer_encoding, "chunked"); + res.prepare_payload(); + BEAST_EXPECT(res[field::content_length] == "50"); + BEAST_EXPECT(res.count(field::transfer_encoding) == 0); + } + + // OK, unsized + { + response res; + res.version = 11; + + res.prepare_payload(); + BEAST_EXPECT(res.count(field::content_length) == 0); + BEAST_EXPECT(res[field::transfer_encoding] == "chunked"); + } + } + + void + testKeepAlive() + { + response res; + auto const keep_alive = + [&](bool v) + { + res.keep_alive(v); + BEAST_EXPECT( + (res.keep_alive() && v) || + (! res.keep_alive() && ! v)); + }; + + BOOST_STATIC_ASSERT(fields::max_static_buffer == 4096); + std::string const big(4096 + 1, 'a'); + + // HTTP/1.0 + res.version = 10; + res.erase(field::connection); + + keep_alive(false); + BEAST_EXPECT(res.count(field::connection) == 0); + + res.set(field::connection, "close"); + keep_alive(false); + BEAST_EXPECT(res.count(field::connection) == 0); + + res.set(field::connection, "keep-alive"); + keep_alive(false); + BEAST_EXPECT(res.count(field::connection) == 0); + + res.set(field::connection, "keep-alive, close"); + keep_alive(false); + BEAST_EXPECT(res.count(field::connection) == 0); + + res.erase(field::connection); + keep_alive(true); + BEAST_EXPECT(res[field::connection] == "keep-alive"); + + res.set(field::connection, "close"); + keep_alive(true); + BEAST_EXPECT(res[field::connection] == "keep-alive"); + + res.set(field::connection, "keep-alive"); + keep_alive(true); + BEAST_EXPECT(res[field::connection] == "keep-alive"); + + res.set(field::connection, "keep-alive, close"); + keep_alive(true); + BEAST_EXPECT(res[field::connection] == "keep-alive"); + + auto const test10 = + [&](std::string s) + { + res.set(field::connection, s); + keep_alive(false); + BEAST_EXPECT(res[field::connection] == s); + + res.set(field::connection, s + ", close"); + keep_alive(false); + BEAST_EXPECT(res[field::connection] == s); + + res.set(field::connection, "keep-alive, " + s); + keep_alive(false); + BEAST_EXPECT(res[field::connection] == s); + + res.set(field::connection, "keep-alive, " + s + ", close"); + keep_alive(false); + BEAST_EXPECT(res[field::connection] == s); + + res.set(field::connection, s); + keep_alive(true); + BEAST_EXPECT(res[field::connection] == s + ", keep-alive"); + + res.set(field::connection, s + ", close"); + keep_alive(true); + BEAST_EXPECT(res[field::connection] == s + ", keep-alive"); + + res.set(field::connection, "keep-alive, " + s); + keep_alive(true); + BEAST_EXPECT(res[field::connection] == "keep-alive, " + s); + + res.set(field::connection, "keep-alive, " + s+ ", close"); + keep_alive(true); + BEAST_EXPECT(res[field::connection] == "keep-alive, " + s); + }; + + test10("foo"); + test10(big); + + // HTTP/1.1 + res.version = 11; + + res.erase(field::connection); + keep_alive(true); + BEAST_EXPECT(res.count(field::connection) == 0); + + res.set(field::connection, "close"); + keep_alive(true); + BEAST_EXPECT(res.count(field::connection) == 0); + + res.set(field::connection, "keep-alive"); + keep_alive(true); + BEAST_EXPECT(res.count(field::connection) == 0); + + res.set(field::connection, "keep-alive, close"); + keep_alive(true); + BEAST_EXPECT(res.count(field::connection) == 0); + + res.erase(field::connection); + keep_alive(false); + BEAST_EXPECT(res[field::connection] == "close"); + + res.set(field::connection, "close"); + keep_alive(false); + BEAST_EXPECT(res[field::connection] == "close"); + + res.set(field::connection, "keep-alive"); + keep_alive(false); + BEAST_EXPECT(res[field::connection] == "close"); + + res.set(field::connection, "keep-alive, close"); + keep_alive(false); + BEAST_EXPECT(res[field::connection] == "close"); + + auto const test11 = + [&](std::string s) + { + res.set(field::connection, s); + keep_alive(true); + BEAST_EXPECT(res[field::connection] == s); + + res.set(field::connection, s + ", close"); + keep_alive(true); + BEAST_EXPECT(res[field::connection] == s); + + res.set(field::connection, "keep-alive, " + s); + keep_alive(true); + BEAST_EXPECT(res[field::connection] == s); + + res.set(field::connection, "keep-alive, " + s + ", close"); + keep_alive(true); + BEAST_EXPECT(res[field::connection] == s); + + res.set(field::connection, s); + keep_alive(false); + BEAST_EXPECT(res[field::connection] == s + ", close"); + + res.set(field::connection, "close, " + s); + keep_alive(false); + BEAST_EXPECT(res[field::connection] == "close, " + s); + + res.set(field::connection, "keep-alive, " + s); + keep_alive(false); + BEAST_EXPECT(res[field::connection] == s + ", close"); + + res.set(field::connection, "close, " + s + ", keep-alive"); + keep_alive(false); + BEAST_EXPECT(res[field::connection] == "close, " + s); + }; + + test11("foo"); + test11(big); + } + + void + testContentLength() + { + response res{status::ok, 11}; + BEAST_EXPECT(res.count(field::content_length) == 0); + BEAST_EXPECT(res.count(field::transfer_encoding) == 0); + + res.content_length(0); + BEAST_EXPECT(res[field::content_length] == "0"); + + res.content_length(100); + BEAST_EXPECT(res[field::content_length] == "100"); + + res.content_length(boost::none); + BEAST_EXPECT(res.count(field::content_length) == 0); + + res.set(field::transfer_encoding, "chunked"); + res.content_length(0); + BEAST_EXPECT(res[field::content_length] == "0"); + BEAST_EXPECT(res.count(field::transfer_encoding) == 0); + + res.set(field::transfer_encoding, "chunked"); + res.content_length(100); + BEAST_EXPECT(res[field::content_length] == "100"); + BEAST_EXPECT(res.count(field::transfer_encoding) == 0); + + res.set(field::transfer_encoding, "chunked"); + res.content_length(boost::none); + BEAST_EXPECT(res.count(field::content_length) == 0); + BEAST_EXPECT(res.count(field::transfer_encoding) == 0); + + auto const check = [&](std::string s) + { + res.set(field::transfer_encoding, s); + res.content_length(0); + BEAST_EXPECT(res[field::content_length] == "0"); + BEAST_EXPECT(res[field::transfer_encoding] == s); + + res.set(field::transfer_encoding, s); + res.content_length(100); + BEAST_EXPECT(res[field::content_length] == "100"); + BEAST_EXPECT(res[field::transfer_encoding] == s); + + res.set(field::transfer_encoding, s); + res.content_length(boost::none); + BEAST_EXPECT(res.count(field::content_length) == 0); + BEAST_EXPECT(res[field::transfer_encoding] == s); + + res.set(field::transfer_encoding, s + ", chunked"); + res.content_length(0); + BEAST_EXPECT(res[field::content_length] == "0"); + BEAST_EXPECT(res[field::transfer_encoding] == s); + + res.set(field::transfer_encoding, s + ", chunked"); + res.content_length(100); + BEAST_EXPECT(res[field::content_length] == "100"); + BEAST_EXPECT(res[field::transfer_encoding] == s); + + res.set(field::transfer_encoding, s + ", chunked"); + res.content_length(boost::none); + BEAST_EXPECT(res.count(field::content_length) == 0); + BEAST_EXPECT(res[field::transfer_encoding] == s); + + res.set(field::transfer_encoding, "chunked, " + s); + res.content_length(0); + BEAST_EXPECT(res[field::content_length] == "0"); + BEAST_EXPECT(res[field::transfer_encoding] == "chunked, " + s); + + res.set(field::transfer_encoding, "chunked, " + s); + res.content_length(100); + BEAST_EXPECT(res[field::content_length] == "100"); + BEAST_EXPECT(res[field::transfer_encoding] == "chunked, " + s); + + res.set(field::transfer_encoding, "chunked, " + s); + res.content_length(boost::none); + BEAST_EXPECT(res.count(field::content_length) == 0); + BEAST_EXPECT(res[field::transfer_encoding] == "chunked, " + s); + }; + + check("foo"); + + BOOST_STATIC_ASSERT(fields::max_static_buffer == 4096); + std::string const big(4096 + 1, 'a'); + + check(big); + } + + void + testChunked() + { + response res{status::ok, 11}; + BEAST_EXPECT(res.count(field::content_length) == 0); + BEAST_EXPECT(res.count(field::transfer_encoding) == 0); + + auto const chunked = + [&](bool v) + { + res.chunked(v); + BEAST_EXPECT( + (res.chunked() && v) || + (! res.chunked() && ! v)); + BEAST_EXPECT(res.count( + field::content_length) == 0); + }; + + res.erase(field::transfer_encoding); + res.set(field::content_length, 32); + chunked(true); + BEAST_EXPECT(res[field::transfer_encoding] == "chunked"); + + res.set(field::transfer_encoding, "chunked"); + chunked(true); + BEAST_EXPECT(res[field::transfer_encoding] == "chunked"); + + res.erase(field::transfer_encoding); + res.set(field::content_length, 32); + chunked(false); + BEAST_EXPECT(res.count(field::transfer_encoding) == 0); + + res.set(field::transfer_encoding, "chunked"); + chunked(false); + BEAST_EXPECT(res.count(field::transfer_encoding) == 0); + + + + res.set(field::transfer_encoding, "foo"); + chunked(true); + BEAST_EXPECT(res[field::transfer_encoding] == "foo, chunked"); + + res.set(field::transfer_encoding, "chunked, foo"); + chunked(true); + BEAST_EXPECT(res[field::transfer_encoding] == "chunked, foo, chunked"); + + res.set(field::transfer_encoding, "chunked, foo, chunked"); + chunked(true); + BEAST_EXPECT(res[field::transfer_encoding] == "chunked, foo, chunked"); + + res.set(field::transfer_encoding, "foo, chunked"); + chunked(false); + BEAST_EXPECT(res[field::transfer_encoding] == "foo"); + + res.set(field::transfer_encoding, "chunked, foo"); + chunked(false); + BEAST_EXPECT(res[field::transfer_encoding] == "chunked, foo"); + + res.set(field::transfer_encoding, "chunked, foo, chunked"); + chunked(false); + BEAST_EXPECT(res[field::transfer_encoding] == "chunked, foo"); + } + + void + run() override + { + testMembers(); + testHeaders(); + testRFC2616(); + testErase(); + testContainer(); + testPreparePayload(); + + testKeepAlive(); + testContentLength(); + testChunked(); + } +}; + +BEAST_DEFINE_TESTSUITE(fields,http,beast); + +} // http +} // beast diff --git a/test/http/file_body.cpp b/test/http/file_body.cpp new file mode 100644 index 0000000000..3555be7108 --- /dev/null +++ b/test/http/file_body.cpp @@ -0,0 +1,112 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +class file_body_test : public beast::unit_test::suite +{ +public: + struct lambda + { + flat_buffer buffer; + + template + void + operator()(error_code&, ConstBufferSequence const& buffers) + { + buffer.commit(boost::asio::buffer_copy( + buffer.prepare(boost::asio::buffer_size(buffers)), + buffers)); + } + }; + + template + void + doTestFileBody() + { + error_code ec; + string_view const s = + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "Content-Length: 3\r\n" + "\r\n" + "xyz"; + auto const temp = boost::filesystem::unique_path(); + { + response_parser> p; + p.eager(true); + + p.get().body.open( + temp.string().c_str(), file_mode::write, ec); + BEAST_EXPECTS(! ec, ec.message()); + + p.put(boost::asio::buffer(s.data(), s.size()), ec); + BEAST_EXPECTS(! ec, ec.message()); + } + { + File f; + f.open(temp.string().c_str(), file_mode::read, ec); + auto size = f.size(ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(size == 3); + std::string s1; + s1.resize(3); + f.read(&s1[0], s1.size(), ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECTS(s1 == "xyz", s); + } + { + lambda visit; + { + response> res{status::ok, 11}; + res.set(field::server, "test"); + res.body.open(temp.string().c_str(), + file_mode::scan, ec); + BEAST_EXPECTS(! ec, ec.message()); + res.prepare_payload(); + + serializer, fields> sr{res}; + sr.next(ec, visit); + BEAST_EXPECTS(! ec, ec.message()); + auto const cb = *visit.buffer.data().begin(); + string_view const s1{ + boost::asio::buffer_cast(cb), + boost::asio::buffer_size(cb)}; + BEAST_EXPECTS(s1 == s, s1); + } + } + boost::filesystem::remove(temp, ec); + BEAST_EXPECTS(! ec, ec.message()); + } + void + run() override + { + doTestFileBody(); + #if BEAST_USE_WIN32_FILE + doTestFileBody(); + #endif + #if BEAST_USE_POSIX_FILE + doTestFileBody(); + #endif + } +}; + +BEAST_DEFINE_TESTSUITE(file_body,http,beast); + +} // http +} // beast diff --git a/test/http/header_parser_v1.cpp b/test/http/header_parser_v1.cpp deleted file mode 100644 index 8200fe8c1a..0000000000 --- a/test/http/header_parser_v1.cpp +++ /dev/null @@ -1,90 +0,0 @@ -// -// 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) -// - -// Test that header file is self-contained. -#include - -#include -#include -#include - -namespace beast { -namespace http { - -class header_parser_v1_test : public beast::unit_test::suite -{ -public: - void testParser() - { - { - error_code ec; - header_parser_v1 p; - BEAST_EXPECT(! p.complete()); - auto const n = p.write(boost::asio::buffer( - "GET / HTTP/1.1\r\n" - "User-Agent: test\r\n" - "\r\n" - ), ec); - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.complete()); - BEAST_EXPECT(n == 36); - } - { - error_code ec; - header_parser_v1 p; - BEAST_EXPECT(! p.complete()); - auto const n = p.write(boost::asio::buffer( - "GET / HTTP/1.1\r\n" - "User-Agent: test\r\n" - "Content-Length: 5\r\n" - "\r\n" - "*****" - ), ec); - BEAST_EXPECT(n == 55); - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.complete()); - } - { - error_code ec; - header_parser_v1 p; - BEAST_EXPECT(! p.complete()); - auto const n = p.write(boost::asio::buffer( - "HTTP/1.1 200 OK\r\n" - "Server: test\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(n == 33); - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.complete()); - } - { - error_code ec; - header_parser_v1 p; - BEAST_EXPECT(! p.complete()); - auto const n = p.write(boost::asio::buffer( - "HTTP/1.1 200 OK\r\n" - "Server: test\r\n" - "Content-Length: 5\r\n" - "\r\n" - "*****" - ), ec); - BEAST_EXPECT(n == 52); - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.complete()); - } - } - - void run() override - { - testParser(); - } -}; - -BEAST_DEFINE_TESTSUITE(header_parser_v1,http,beast); - -} // http -} // beast diff --git a/test/http/message.cpp b/test/http/message.cpp index 3b1c711af8..18159fee95 100644 --- a/test/http/message.cpp +++ b/test/http/message.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -70,212 +71,223 @@ public: }; }; - void testMessage() + // 0-arg + BOOST_STATIC_ASSERT(std::is_constructible< + request>::value); + + // 1-arg + BOOST_STATIC_ASSERT(! std::is_constructible + >::value); + + //BOOST_STATIC_ASSERT(! std::is_constructible, + // verb, string_view, unsigned>::value); + + BOOST_STATIC_ASSERT(std::is_constructible, + verb, string_view, unsigned, Arg1>::value); + + BOOST_STATIC_ASSERT(std::is_constructible, + verb, string_view, unsigned, Arg1&&>::value); + + BOOST_STATIC_ASSERT(std::is_constructible, + verb, string_view, unsigned, Arg1 const>::value); + + BOOST_STATIC_ASSERT(std::is_constructible, + verb, string_view, unsigned, Arg1 const&>::value); + + // 1-arg + fields + BOOST_STATIC_ASSERT(std::is_constructible, + verb, string_view, unsigned, Arg1, fields::allocator_type>::value); + + BOOST_STATIC_ASSERT(std::is_constructible, std::piecewise_construct_t, + std::tuple>::value); + + BOOST_STATIC_ASSERT(std::is_constructible, std::piecewise_construct_t, + std::tuple>::value); + + BOOST_STATIC_ASSERT(std::is_constructible, std::piecewise_construct_t, + std::tuple, std::tuple>::value); + + // special members + BOOST_STATIC_ASSERT(std::is_copy_constructible>::value); + BOOST_STATIC_ASSERT(std::is_move_constructible>::value); + BOOST_STATIC_ASSERT(std::is_copy_assignable>::value); + BOOST_STATIC_ASSERT(std::is_move_assignable>::value); + BOOST_STATIC_ASSERT(std::is_copy_constructible>::value); + BOOST_STATIC_ASSERT(std::is_move_constructible>::value); + BOOST_STATIC_ASSERT(std::is_copy_assignable>::value); + BOOST_STATIC_ASSERT(std::is_move_assignable>::value); + + void + testMessage() { - static_assert(std::is_constructible< - message>::value, ""); - - static_assert(std::is_constructible< - message, Arg1>::value, ""); - - static_assert(std::is_constructible< - message, Arg1 const>::value, ""); - - static_assert(std::is_constructible< - message, Arg1 const&>::value, ""); - - static_assert(std::is_constructible< - message, Arg1&&>::value, ""); - - static_assert(! std::is_constructible< - message>::value, ""); - - static_assert(std::is_constructible< - message, - Arg1, fields::allocator_type>::value, ""); - - static_assert(std::is_constructible< - message, std::piecewise_construct_t, - std::tuple>::value, ""); - - static_assert(std::is_constructible< - message, std::piecewise_construct_t, - std::tuple>::value, ""); - - static_assert(std::is_constructible< - message, std::piecewise_construct_t, - std::tuple, std::tuple>::value, ""); - { Arg1 arg1; - message{std::move(arg1)}; + request{verb::get, "/", 11, std::move(arg1)}; BEAST_EXPECT(arg1.moved); } { - fields h; - h.insert("User-Agent", "test"); - message m{Arg1{}, h}; - BEAST_EXPECT(h["User-Agent"] == "test"); - BEAST_EXPECT(m.fields["User-Agent"] == "test"); + header h; + h.set(field::user_agent, "test"); + BEAST_EXPECT(h[field::user_agent] == "test"); + request m{std::move(h)}; + BEAST_EXPECT(m[field::user_agent] == "test"); + BEAST_EXPECT(h.count(field::user_agent) == 0); } { - fields h; - h.insert("User-Agent", "test"); - message m{Arg1{}, std::move(h)}; - BEAST_EXPECT(! h.exists("User-Agent")); - BEAST_EXPECT(m.fields["User-Agent"] == "test"); + request h{verb::get, "/", 10}; + h.set(field::user_agent, "test"); + request m{std::move(h.base()), Arg1{}}; + BEAST_EXPECT(m["User-Agent"] == "test"); + BEAST_EXPECT(h.count(http::field::user_agent) == 0); + BEAST_EXPECT(m.method() == verb::get); + BEAST_EXPECT(m.target() == "/"); + BEAST_EXPECT(m.version == 10); } // swap - message m1; - message m2; - m1.url = "u"; + request m1; + request m2; + m1.target("u"); m1.body = "1"; - m1.fields.insert("h", "v"); - m2.method = "G"; + m1.insert("h", "v"); + m2.method_string("G"); m2.body = "2"; swap(m1, m2); - BEAST_EXPECT(m1.method == "G"); - BEAST_EXPECT(m2.method.empty()); - BEAST_EXPECT(m1.url.empty()); - BEAST_EXPECT(m2.url == "u"); + BEAST_EXPECT(m1.method_string() == "G"); + BEAST_EXPECT(m2.method_string().empty()); + BEAST_EXPECT(m1.target().empty()); + BEAST_EXPECT(m2.target() == "u"); BEAST_EXPECT(m1.body == "2"); BEAST_EXPECT(m2.body == "1"); - BEAST_EXPECT(! m1.fields.exists("h")); - BEAST_EXPECT(m2.fields.exists("h")); + BEAST_EXPECT(! m1.count("h")); + BEAST_EXPECT(m2.count("h")); } - struct MoveHeaders + struct MoveFields : fields { bool moved_to = false; bool moved_from = false; - MoveHeaders() = default; + MoveFields() = default; - MoveHeaders(MoveHeaders&& other) + MoveFields(MoveFields&& other) : moved_to(true) { other.moved_from = true; } - MoveHeaders& operator=(MoveHeaders&& other) + MoveFields& operator=(MoveFields&&) { return *this; } }; - void testHeaders() + struct token {}; + + struct test_fields + { + std::string target; + + test_fields() = delete; + test_fields(token) {} + string_view get_method_impl() const { return {}; } + string_view get_target_impl() const { return target; } + string_view get_reason_impl() const { return {}; } + bool get_chunked_impl() const { return false; } + bool get_keep_alive_impl(unsigned) const { return true; } + void set_method_impl(string_view) {} + void set_target_impl(string_view s) { target = s.to_string(); } + void set_reason_impl(string_view) {} + void set_chunked_impl(bool) {} + void set_content_length_impl(boost::optional) {} + void set_keep_alive_impl(unsigned, bool) {} + }; + + void + testMessageCtors() { { - using req_type = request_header; - static_assert(std::is_copy_constructible::value, ""); - static_assert(std::is_move_constructible::value, ""); - static_assert(std::is_copy_assignable::value, ""); - static_assert(std::is_move_assignable::value, ""); - - using res_type = response_header; - static_assert(std::is_copy_constructible::value, ""); - static_assert(std::is_move_constructible::value, ""); - static_assert(std::is_copy_assignable::value, ""); - static_assert(std::is_move_assignable::value, ""); + request req; + BEAST_EXPECT(req.version == 11); + BEAST_EXPECT(req.method() == verb::unknown); + BEAST_EXPECT(req.target() == ""); } - { - MoveHeaders h; - header r{std::move(h)}; - BEAST_EXPECT(h.moved_from); - BEAST_EXPECT(r.fields.moved_to); - request m{std::move(r)}; - BEAST_EXPECT(r.fields.moved_from); - BEAST_EXPECT(m.fields.moved_to); + request req{verb::get, "/", 11}; + BEAST_EXPECT(req.version == 11); + BEAST_EXPECT(req.method() == verb::get); + BEAST_EXPECT(req.target() == "/"); + } + { + request req{verb::get, "/", 11, "Hello"}; + BEAST_EXPECT(req.version == 11); + BEAST_EXPECT(req.method() == verb::get); + BEAST_EXPECT(req.target() == "/"); + BEAST_EXPECT(req.body == "Hello"); + } + { + request req{ + verb::get, "/", 11, "Hello", token{}}; + BEAST_EXPECT(req.version == 11); + BEAST_EXPECT(req.method() == verb::get); + BEAST_EXPECT(req.target() == "/"); + BEAST_EXPECT(req.body == "Hello"); + } + { + response res; + BEAST_EXPECT(res.version == 11); + BEAST_EXPECT(res.result() == status::ok); + BEAST_EXPECT(res.reason() == "OK"); + } + { + response res{status::bad_request, 10}; + BEAST_EXPECT(res.version == 10); + BEAST_EXPECT(res.result() == status::bad_request); + BEAST_EXPECT(res.reason() == "Bad Request"); + } + { + response res{status::bad_request, 10, "Hello"}; + BEAST_EXPECT(res.version == 10); + BEAST_EXPECT(res.result() == status::bad_request); + BEAST_EXPECT(res.reason() == "Bad Request"); + BEAST_EXPECT(res.body == "Hello"); + } + { + response res{ + status::bad_request, 10, "Hello", token{}}; + BEAST_EXPECT(res.version == 10); + BEAST_EXPECT(res.result() == status::bad_request); + BEAST_EXPECT(res.reason() == "Bad Request"); + BEAST_EXPECT(res.body == "Hello"); } } - void testFreeFunctions() + void + testSwap() { - { - request m; - m.method = "GET"; - m.url = "/"; - m.version = 11; - m.fields.insert("Upgrade", "test"); - BEAST_EXPECT(! is_upgrade(m)); - - prepare(m, connection::upgrade); - BEAST_EXPECT(is_upgrade(m)); - BEAST_EXPECT(m.fields["Connection"] == "upgrade"); - - m.version = 10; - BEAST_EXPECT(! is_upgrade(m)); - } - } - - void testPrepare() - { - request m; - m.version = 10; - BEAST_EXPECT(! is_upgrade(m)); - m.fields.insert("Transfer-Encoding", "chunked"); - try - { - prepare(m); - fail(); - } - catch(std::exception const&) - { - } - m.fields.erase("Transfer-Encoding"); - m.fields.insert("Content-Length", "0"); - try - { - prepare(m); - fail(); - } - catch(std::exception const&) - { - pass(); - } - m.fields.erase("Content-Length"); - m.fields.insert("Connection", "keep-alive"); - try - { - prepare(m); - fail(); - } - catch(std::exception const&) - { - pass(); - } - m.version = 11; - m.fields.erase("Connection"); - m.fields.insert("Connection", "close"); - BEAST_EXPECT(! is_keep_alive(m)); - } - - void testSwap() - { - message m1; - message m2; - m1.status = 200; + response m1; + response m2; + m1.result(status::ok); m1.version = 10; m1.body = "1"; - m1.fields.insert("h", "v"); - m2.status = 404; - m2.reason = "OK"; + m1.insert("h", "v"); + m2.result(status::not_found); m2.body = "2"; m2.version = 11; swap(m1, m2); - BEAST_EXPECT(m1.status == 404); - BEAST_EXPECT(m2.status == 200); - BEAST_EXPECT(m1.reason == "OK"); - BEAST_EXPECT(m2.reason.empty()); + BEAST_EXPECT(m1.result() == status::not_found); + BEAST_EXPECT(m1.result_int() == 404); + BEAST_EXPECT(m2.result() == status::ok); + BEAST_EXPECT(m2.result_int() == 200); + BEAST_EXPECT(m1.reason() == "Not Found"); + BEAST_EXPECT(m2.reason() == "OK"); BEAST_EXPECT(m1.version == 11); BEAST_EXPECT(m2.version == 10); BEAST_EXPECT(m1.body == "2"); BEAST_EXPECT(m2.body == "1"); - BEAST_EXPECT(! m1.fields.exists("h")); - BEAST_EXPECT(m2.fields.exists("h")); + BEAST_EXPECT(! m1.count("h")); + BEAST_EXPECT(m2.count("h")); } void @@ -291,14 +303,70 @@ public: }(); } - void run() override + void + testMethod() + { + header h; + auto const vcheck = + [&](verb v) + { + h.method(v); + BEAST_EXPECT(h.method() == v); + BEAST_EXPECT(h.method_string() == to_string(v)); + }; + auto const scheck = + [&](string_view s) + { + h.method_string(s); + BEAST_EXPECT(h.method() == string_to_verb(s)); + BEAST_EXPECT(h.method_string() == s); + }; + vcheck(verb::get); + vcheck(verb::head); + scheck("GET"); + scheck("HEAD"); + scheck("XYZ"); + } + + void + testStatus() + { + header h; + h.result(200); + BEAST_EXPECT(h.result_int() == 200); + BEAST_EXPECT(h.result() == status::ok); + h.result(status::switching_protocols); + BEAST_EXPECT(h.result_int() == 101); + BEAST_EXPECT(h.result() == status::switching_protocols); + h.result(1); + BEAST_EXPECT(h.result_int() == 1); + BEAST_EXPECT(h.result() == status::unknown); + } + + void + testReason() + { + header h; + h.result(status::ok); + BEAST_EXPECT(h.reason() == "OK"); + h.reason("Pepe"); + BEAST_EXPECT(h.reason() == "Pepe"); + h.result(status::not_found); + BEAST_EXPECT(h.reason() == "Pepe"); + h.reason({}); + BEAST_EXPECT(h.reason() == "Not Found"); + } + + void + run() override { testMessage(); - testHeaders(); - testFreeFunctions(); - testPrepare(); + testMessageCtors(); testSwap(); testSpecialMembers(); + testMethod(); + testStatus(); + testReason(); } }; diff --git a/test/http/message_fuzz.hpp b/test/http/message_fuzz.hpp index c0780a08d0..2c1ff25b0b 100644 --- a/test/http/message_fuzz.hpp +++ b/test/http/message_fuzz.hpp @@ -8,8 +8,7 @@ #ifndef BEAST_HTTP_TEST_MESSAGE_FUZZ_HPP #define BEAST_HTTP_TEST_MESSAGE_FUZZ_HPP -#include -#include +#include #include #include #include @@ -20,7 +19,7 @@ namespace http { template std::string -escaped_string(boost::string_ref const& s) +escaped_string(string_view s) { std::string out; out.reserve(s.size()); @@ -132,7 +131,7 @@ public: "msrp", "msrps", "mtqp", "mumble", "mupdate", "mvn", "news", "nfs", "ni", "nih", "nntp", "notes", "oid", "opaquelocktoken", "pack", "palm", "paparazzi", "pkcs11", "platform", "pop", "pres", "prospero", "proxy", "psyc", "query", - "redis", "rediss", "reload", "res", "resource", "rmi", "rsync", "rtmfp", + "redis", "rediss", "reload", "res", "target", "rmi", "rsync", "rtmfp", "rtmp", "rtsp", "rtsps", "rtspu", "secondlife", "service", "session", "sftp", "sgn", "shttp", "sieve", "sip", "sips", "skype", "smb", "sms", "smtp", "snews", "snmp", "soap.beep", "soap.beeps", "soldat", "spotify", "ssh", @@ -320,7 +319,7 @@ public: } std::string - uri() + target() { //switch(rand(4)) switch(1) @@ -349,7 +348,7 @@ public: #if 0 std::string - uri() + target() { static char constexpr alpha[63] = "0123456789" "ABCDEFGHIJ" "KLMNOPQRST" @@ -477,13 +476,13 @@ public: void fields(DynamicBuffer& db) { + auto os = ostream(db); while(rand(6)) - { - write(db, field()); - write(db, rand(4) ? ": " : ":"); - write(db, value()); - write(db, "\r\n"); - } + os << + field() << + (rand(4) ? ": " : ":") << + value() << + "\r\n"; } template @@ -492,14 +491,16 @@ public: { if(! rand(4)) { - write(db, "Content-Length: 0\r\n\r\n"); + ostream(db) << + "Content-Length: 0\r\n\r\n"; return; } if(rand(2)) { auto const len = rand(500); - write(db, "Content-Length: ", len, "\r\n\r\n"); - for(auto const& b : db.prepare(len)) + ostream(db) << + "Content-Length: " << len << "\r\n\r\n"; + for(boost::asio::mutable_buffer b : db.prepare(len)) { auto p = boost::asio::buffer_cast(b); auto n = boost::asio::buffer_size(b); @@ -511,13 +512,15 @@ public: else { auto len = rand(500); - write(db, "Transfer-Encoding: chunked\r\n\r\n"); + ostream(db) << + "Transfer-Encoding: chunked\r\n\r\n"; while(len > 0) { auto n = (std::min)(1 + rand(300), len); len -= n; - write(db, to_hex(n), "\r\n"); - for(auto const& b : db.prepare(n)) + ostream(db) << + to_hex(n) << "\r\n"; + for(boost::asio::mutable_buffer b : db.prepare(n)) { auto p = boost::asio::buffer_cast(b); auto m = boost::asio::buffer_size(b); @@ -525,9 +528,9 @@ public: *p++ = static_cast(32 + rand(26+26+10+6)); } db.commit(n); - write(db, "\r\n"); + ostream(db) << "\r\n"; } - write(db, "0\r\n\r\n"); + ostream(db) << "0\r\n\r\n"; } } @@ -535,7 +538,8 @@ public: void request(DynamicBuffer& db) { - write(db, method(), " ", uri(), " HTTP/1.1\r\n"); + ostream(db) << + method() << " " << target() << " HTTP/1.1\r\n"; fields(db); body(db); } @@ -544,14 +548,15 @@ public: void response(DynamicBuffer& db) { - write(db, "HTTP/1."); - write(db, rand(2) ? "0" : "1"); - write(db, " ", 100 + rand(401), " "); - write(db, token()); - write(db, "\r\n"); + ostream(db) << + "HTTP/1." << + (rand(2) ? "0" : "1") << " " << + (100 + rand(401)) << " " << + token() << + "\r\n"; fields(db); body(db); - write(db, "\r\n"); + ostream(db) << "\r\n"; } }; diff --git a/test/http/parse_error.cpp b/test/http/parse_error.cpp deleted file mode 100644 index ea23bc1a1c..0000000000 --- a/test/http/parse_error.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// -// 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) -// - -// Test that header file is self-contained. -#include - -#include -#include - -namespace beast { -namespace http { - -class parse_error_test : public unit_test::suite -{ -public: - void check(char const* name, parse_error ev) - { - auto const ec = make_error_code(ev); - BEAST_EXPECT(std::string{ec.category().name()} == name); - BEAST_EXPECT(! ec.message().empty()); - BEAST_EXPECT(std::addressof(ec.category()) == - std::addressof(detail::get_parse_error_category())); - BEAST_EXPECT(detail::get_parse_error_category().equivalent( - static_cast::type>(ev), - ec.category().default_error_condition( - static_cast::type>(ev)))); - BEAST_EXPECT(detail::get_parse_error_category().equivalent( - ec, static_cast::type>(ev))); - } - - void run() override - { - check("http", parse_error::connection_closed); - check("http", parse_error::bad_method); - check("http", parse_error::bad_uri); - check("http", parse_error::bad_version); - check("http", parse_error::bad_crlf); - check("http", parse_error::bad_status); - check("http", parse_error::bad_reason); - check("http", parse_error::bad_field); - check("http", parse_error::bad_value); - check("http", parse_error::bad_content_length); - check("http", parse_error::illegal_content_length); - check("http", parse_error::invalid_chunk_size); - check("http", parse_error::invalid_ext_name); - check("http", parse_error::invalid_ext_val); - check("http", parse_error::header_too_big); - check("http", parse_error::body_too_big); - check("http", parse_error::short_read); - } -}; - -BEAST_DEFINE_TESTSUITE(parse_error,http,beast); - -} // http -} // beast diff --git a/test/http/parser.cpp b/test/http/parser.cpp new file mode 100644 index 0000000000..cae9d90457 --- /dev/null +++ b/test/http/parser.cpp @@ -0,0 +1,389 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include + +#include "test_parser.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +class parser_test + : public beast::unit_test::suite + , public beast::test::enable_yield_to +{ +public: + template + using parser_type = + parser; + + static + boost::asio::const_buffers_1 + buf(string_view s) + { + return {s.data(), s.size()}; + } + + template + static + void + put(ConstBufferSequence const& buffers, + basic_parser& p, + error_code& ec) + { + using boost::asio::buffer_size; + consuming_buffers cb{buffers}; + for(;;) + { + auto const used = p.put(cb, ec); + cb.consume(used); + if(ec) + return; + if(p.need_eof() && + buffer_size(cb) == 0) + { + p.put_eof(ec); + if(ec) + return; + } + if(p.is_done()) + break; + } + } + + template + void + doMatrix(string_view s0, F const& f) + { + using boost::asio::buffer; + // parse a single buffer + { + auto s = s0; + error_code ec; + parser_type p; + put(buffer(s.data(), s.size()), p, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + f(p); + } + // parse two buffers + for(auto n = s0.size() - 1; n >= 1; --n) + { + auto s = s0; + error_code ec; + parser_type p; + p.eager(true); + auto used = + p.put(buffer(s.data(), n), ec); + s.remove_prefix(used); + if(ec == error::need_more) + ec.assign(0, ec.category()); + if(! BEAST_EXPECTS(! ec, ec.message())) + continue; + BEAST_EXPECT(! p.is_done()); + used = p.put( + buffer(s.data(), s.size()), ec); + s.remove_prefix(used); + if(! BEAST_EXPECTS(! ec, ec.message())) + continue; + BEAST_EXPECT(s.empty()); + if(p.need_eof()) + { + p.put_eof(ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + continue; + } + if(BEAST_EXPECT(p.is_done())) + f(p); + } + } + + void + testParse() + { + doMatrix( + "HTTP/1.0 200 OK\r\n" + "Server: test\r\n" + "\r\n" + "Hello, world!", + [&](parser_type const& p) + { + auto const& m = p.get(); + BEAST_EXPECT(! p.is_chunked()); + BEAST_EXPECT(p.need_eof()); + BEAST_EXPECT(p.content_length() == boost::none); + BEAST_EXPECT(m.version == 10); + BEAST_EXPECT(m.result() == status::ok); + BEAST_EXPECT(m.reason() == "OK"); + BEAST_EXPECT(m["Server"] == "test"); + BEAST_EXPECT(m.body == "Hello, world!"); + } + ); + doMatrix( + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "Expect: Expires, MD5-Fingerprint\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "5\r\n" + "*****\r\n" + "2;a;b=1;c=\"2\"\r\n" + "--\r\n" + "0;d;e=3;f=\"4\"\r\n" + "Expires: never\r\n" + "MD5-Fingerprint: -\r\n" + "\r\n", + [&](parser_type const& p) + { + auto const& m = p.get(); + BEAST_EXPECT(! p.need_eof()); + BEAST_EXPECT(p.is_chunked()); + BEAST_EXPECT(p.content_length() == boost::none); + BEAST_EXPECT(m.version == 11); + BEAST_EXPECT(m.result() == status::ok); + BEAST_EXPECT(m.reason() == "OK"); + BEAST_EXPECT(m["Server"] == "test"); + BEAST_EXPECT(m["Transfer-Encoding"] == "chunked"); + BEAST_EXPECT(m["Expires"] == "never"); + BEAST_EXPECT(m["MD5-Fingerprint"] == "-"); + BEAST_EXPECT(m.body == "*****--"); + } + ); + doMatrix( + "HTTP/1.0 200 OK\r\n" + "Server: test\r\n" + "Content-Length: 5\r\n" + "\r\n" + "*****", + [&](parser_type const& p) + { + auto const& m = p.get(); + BEAST_EXPECT(m.body == "*****"); + } + ); + doMatrix( + "GET / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "\r\n", + [&](parser_type const& p) + { + auto const& m = p.get(); + BEAST_EXPECT(m.method() == verb::get); + BEAST_EXPECT(m.target() == "/"); + BEAST_EXPECT(m.version == 11); + BEAST_EXPECT(! p.need_eof()); + BEAST_EXPECT(! p.is_chunked()); + BEAST_EXPECT(p.content_length() == boost::none); + } + ); + doMatrix( + "GET / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "X: \t x \t \r\n" + "\r\n", + [&](parser_type const& p) + { + auto const& m = p.get(); + BEAST_EXPECT(m["X"] == "x"); + } + ); + + // test eager(true) + { + error_code ec; + parser_type p; + p.eager(true); + p.put(buf( + "GET / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*") + , ec); + auto const& m = p.get(); + BEAST_EXPECT(! ec); + BEAST_EXPECT(p.is_done()); + BEAST_EXPECT(p.is_header_done()); + BEAST_EXPECT(! p.need_eof()); + BEAST_EXPECT(m.method() == verb::get); + BEAST_EXPECT(m.target() == "/"); + BEAST_EXPECT(m.version == 11); + BEAST_EXPECT(m["User-Agent"] == "test"); + BEAST_EXPECT(m.body == "*"); + } + { + // test partial parsing of final chunk + // parse through the chunk body + error_code ec; + flat_buffer b; + parser_type p; + p.eager(true); + ostream(b) << + "PUT / HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "1\r\n" + "*"; + auto used = p.put(b.data(), ec); + b.consume(used); + BEAST_EXPECT(! ec); + BEAST_EXPECT(! p.is_done()); + BEAST_EXPECT(p.get().body == "*"); + ostream(b) << + "\r\n" + "0;d;e=3;f=\"4\"\r\n" + "Expires: never\r\n" + "MD5-Fingerprint: -\r\n"; + // incomplete parse, missing the final crlf + used = p.put(b.data(), ec); + b.consume(used); + BEAST_EXPECT(ec == error::need_more); + ec.assign(0, ec.category()); + BEAST_EXPECT(! p.is_done()); + ostream(b) << + "\r\n"; // final crlf to end message + used = p.put(b.data(), ec); + b.consume(used); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_done()); + } + // skip body + { + error_code ec; + response_parser p; + p.skip(true); + p.put(buf( + "HTTP/1.1 200 OK\r\n" + "Content-Length: 5\r\n" + "\r\n" + "*****") + , ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_done()); + BEAST_EXPECT(p.is_header_done()); + BEAST_EXPECT(p.content_length() && + *p.content_length() == 5); + } + } + + //-------------------------------------------------------------------------- + + template + void + testNeedMore() + { + error_code ec; + std::size_t used; + { + DynamicBuffer b; + parser_type p; + ostream(b) << + "GET / HTTP/1.1\r\n"; + used = p.put(b.data(), ec); + BEAST_EXPECTS(ec == error::need_more, ec.message()); + b.consume(used); + ec.assign(0, ec.category()); + ostream(b) << + "User-Agent: test\r\n" + "\r\n"; + used = p.put(b.data(), ec); + BEAST_EXPECTS(! ec, ec.message()); + b.consume(used); + BEAST_EXPECT(p.is_done()); + BEAST_EXPECT(p.is_header_done()); + } + } + + void + testGotSome() + { + error_code ec; + parser_type p; + auto used = p.put(buf(""), ec); + BEAST_EXPECT(ec == error::need_more); + BEAST_EXPECT(! p.got_some()); + BEAST_EXPECT(used == 0); + ec.assign(0, ec.category()); + used = p.put(buf("G"), ec); + BEAST_EXPECT(ec == error::need_more); + BEAST_EXPECT(p.got_some()); + BEAST_EXPECT(used == 0); + } + + void + testCallback() + { + { + multi_buffer b; + ostream(b) << + "POST / HTTP/1.1\r\n" + "Content-Length: 2\r\n" + "\r\n" + "**"; + error_code ec; + parser p; + p.eager(true); + p.put(b.data(), ec); + p.on_header( + [this](parser& p, error_code& ec) + { + BEAST_EXPECT(p.is_header_done()); + ec.assign(0, ec.category()); + }); + BEAST_EXPECTS(! ec, ec.message()); + } + { + multi_buffer b; + ostream(b) << + "POST / HTTP/1.1\r\n" + "Content-Length: 2\r\n" + "\r\n" + "**"; + error_code ec; + parser p; + p.eager(true); + p.put(b.data(), ec); + p.on_header( + [this](parser&, error_code& ec) + { + ec.assign(errc::bad_message, + generic_category()); + }); + BEAST_EXPECTS(! ec, ec.message()); + } + } + + void + run() override + { + testParse(); + testNeedMore(); + testNeedMore(); + testGotSome(); + testCallback(); + } +}; + +BEAST_DEFINE_TESTSUITE(parser,http,beast); + +} // http +} // beast + diff --git a/test/http/parser_bench.cpp b/test/http/parser_bench.cpp deleted file mode 100644 index 768d267d70..0000000000 --- a/test/http/parser_bench.cpp +++ /dev/null @@ -1,155 +0,0 @@ -// -// 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 "nodejs_parser.hpp" -#include "message_fuzz.hpp" - -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -class parser_bench_test : public beast::unit_test::suite -{ -public: - static std::size_t constexpr N = 2000; - - using corpus = std::vector; - - corpus creq_; - corpus cres_; - std::size_t size_ = 0; - - parser_bench_test() - { - creq_ = build_corpus(N/2, std::true_type{}); - cres_ = build_corpus(N/2, std::false_type{}); - } - - corpus - build_corpus(std::size_t n, std::true_type) - { - corpus v; - v.resize(N); - message_fuzz mg; - for(std::size_t i = 0; i < n; ++i) - { - mg.request(v[i]); - size_ += v[i].size(); - } - return v; - } - - corpus - build_corpus(std::size_t n, std::false_type) - { - corpus v; - v.resize(N); - message_fuzz mg; - for(std::size_t i = 0; i < n; ++i) - { - mg.response(v[i]); - size_ += v[i].size(); - } - return v; - } - - template - void - testParser(std::size_t repeat, corpus const& v) - { - while(repeat--) - for(auto const& sb : v) - { - Parser p; - error_code ec; - p.write(sb.data(), ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - log << to_string(sb.data()) << std::endl; - } - } - - template - void - timedTest(std::size_t repeat, std::string const& name, Function&& f) - { - using namespace std::chrono; - using clock_type = std::chrono::high_resolution_clock; - log << name << std::endl; - for(std::size_t trial = 1; trial <= repeat; ++trial) - { - auto const t0 = clock_type::now(); - f(); - auto const elapsed = clock_type::now() - t0; - log << - "Trial " << trial << ": " << - duration_cast(elapsed).count() << " ms" << std::endl; - } - } - - template - struct null_parser : basic_parser_v1> - { - }; - - void - testSpeed() - { - static std::size_t constexpr Trials = 3; - static std::size_t constexpr Repeat = 50; - - log << "sizeof(request parser) == " << - sizeof(basic_parser_v1>) << '\n'; - - log << "sizeof(response parser) == " << - sizeof(basic_parser_v1>)<< '\n'; - - testcase << "Parser speed test, " << - ((Repeat * size_ + 512) / 1024) << "KB in " << - (Repeat * (creq_.size() + cres_.size())) << " messages"; - - timedTest(Trials, "nodejs_parser", - [&] - { - testParser>( - Repeat, creq_); - testParser>( - Repeat, cres_); - }); - timedTest(Trials, "http::basic_parser_v1", - [&] - { - testParser>( - Repeat, creq_); - testParser>( - Repeat, cres_); - }); - pass(); - } - - void run() override - { - pass(); - testSpeed(); - } -}; - -BEAST_DEFINE_TESTSUITE(parser_bench,http,beast); - -} // http -} // beast - diff --git a/test/http/parser_v1.cpp b/test/http/parser_v1.cpp deleted file mode 100644 index 20c207d7d1..0000000000 --- a/test/http/parser_v1.cpp +++ /dev/null @@ -1,160 +0,0 @@ -// -// 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) -// - -// Test that header file is self-contained. -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -class parser_v1_test - : public beast::unit_test::suite - , public test::enable_yield_to -{ -public: - void - testParse() - { - using boost::asio::buffer; - { - error_code ec; - parser_v1>> p; - std::string const s = - "GET / HTTP/1.1\r\n" - "User-Agent: test\r\n" - "Content-Length: 1\r\n" - "\r\n" - "*"; - p.write(buffer(s), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - auto m = p.release(); - BEAST_EXPECT(m.method == "GET"); - BEAST_EXPECT(m.url == "/"); - BEAST_EXPECT(m.version == 11); - BEAST_EXPECT(m.fields["User-Agent"] == "test"); - BEAST_EXPECT(m.body == "*"); - } - { - error_code ec; - parser_v1>> p; - std::string const s = - "HTTP/1.1 200 OK\r\n" - "Server: test\r\n" - "Content-Length: 1\r\n" - "\r\n" - "*"; - p.write(buffer(s), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - auto m = p.release(); - BEAST_EXPECT(m.status == 200); - BEAST_EXPECT(m.reason == "OK"); - BEAST_EXPECT(m.version == 11); - BEAST_EXPECT(m.fields["Server"] == "test"); - BEAST_EXPECT(m.body == "*"); - } - // skip body - { - error_code ec; - parser_v1 p; - std::string const s = - "HTTP/1.1 200 Connection Established\r\n" - "Proxy-Agent: Zscaler/5.1\r\n" - "\r\n"; - p.set_option(skip_body{true}); - p.write(buffer(s), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - } - } - - void - testWithBody() - { - std::string const raw = - "GET / HTTP/1.1\r\n" - "User-Agent: test\r\n" - "Content-Length: 1\r\n" - "\r\n" - "*"; - test::string_istream ss{ - ios_, raw, raw.size() - 1}; - - streambuf rb; - header_parser_v1 p0; - parse(ss, rb, p0); - request_header const& reqh = p0.get(); - BEAST_EXPECT(reqh.method == "GET"); - BEAST_EXPECT(reqh.url == "/"); - BEAST_EXPECT(reqh.version == 11); - BEAST_EXPECT(reqh.fields["User-Agent"] == "test"); - BEAST_EXPECT(reqh.fields["Content-Length"] == "1"); - parser_v1 p = - with_body(p0); - BEAST_EXPECT(p.get().method == "GET"); - BEAST_EXPECT(p.get().url == "/"); - BEAST_EXPECT(p.get().version == 11); - BEAST_EXPECT(p.get().fields["User-Agent"] == "test"); - BEAST_EXPECT(p.get().fields["Content-Length"] == "1"); - parse(ss, rb, p); - request req = p.release(); - BEAST_EXPECT(req.body == "*"); - } - - void - testRegressions() - { - using boost::asio::buffer; - - // consecutive empty header values - { - error_code ec; - parser_v1 p; - std::string const s = - "GET / HTTP/1.1\r\n" - "X1:\r\n" - "X2:\r\n" - "X3:x\r\n" - "\r\n"; - p.write(buffer(s), ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - BEAST_EXPECT(p.complete()); - auto const msg = p.release(); - BEAST_EXPECT(msg.fields.exists("X1")); - BEAST_EXPECT(msg.fields["X1"] == ""); - BEAST_EXPECT(msg.fields.exists("X2")); - BEAST_EXPECT(msg.fields["X2"] == ""); - BEAST_EXPECT(msg.fields.exists("X3")); - BEAST_EXPECT(msg.fields["X3"] == "x"); - } - } - - void run() override - { - testParse(); - testWithBody(); - testRegressions(); - } -}; - -BEAST_DEFINE_TESTSUITE(parser_v1,http,beast); - -} // http -} // beast diff --git a/test/http/read.cpp b/test/http/read.cpp index 54356d4f06..3e28ea5083 100644 --- a/test/http/read.cpp +++ b/test/http/read.cpp @@ -8,11 +8,16 @@ // Test that header file is self-contained. #include -#include "fail_parser.hpp" +#include "test_parser.hpp" +#include +#include #include -#include +#include +#include +#include #include +#include #include #include #include @@ -27,62 +32,9 @@ class read_test , public test::enable_yield_to { public: - struct fail_body - { - class reader; - - class value_type - { - friend class reader; - - std::string s_; - test::fail_counter& fc_; - - public: - explicit - value_type(test::fail_counter& fc) - : fc_(fc) - { - } - - value_type& - operator=(std::string s) - { - s_ = std::move(s); - return *this; - } - }; - - class reader - { - value_type& body_; - - public: - template - explicit - reader(message& msg) noexcept - : body_(msg.body) - { - } - - void - init(error_code& ec) noexcept - { - body_.fc_.fail(ec); - } - - void - write(void const* data, - std::size_t size, error_code& ec) noexcept - { - if(body_.fc_.fail(ec)) - return; - } - }; - }; - template - void failMatrix(char const* s, yield_context do_yield) + void + failMatrix(char const* s, yield_context do_yield) { using boost::asio::buffer; using boost::asio::buffer_copy; @@ -91,15 +43,15 @@ public: auto const len = strlen(s); for(n = 0; n < limit; ++n) { - streambuf sb; - sb.commit(buffer_copy( - sb.prepare(len), buffer(s, len))); + multi_buffer b; + b.commit(buffer_copy( + b.prepare(len), buffer(s, len))); test::fail_counter fc(n); test::fail_stream< test::string_istream> fs{fc, ios_, ""}; - fail_parser p(fc); - error_code ec; - parse(fs, sb, p, ec); + test_parser p(fc); + error_code ec = test::error::fail_error; + read(fs, b, p, ec); if(! ec) break; } @@ -107,30 +59,30 @@ public: for(n = 0; n < limit; ++n) { static std::size_t constexpr pre = 10; - streambuf sb; - sb.commit(buffer_copy( - sb.prepare(pre), buffer(s, pre))); + multi_buffer b; + b.commit(buffer_copy( + b.prepare(pre), buffer(s, pre))); test::fail_counter fc(n); test::fail_stream fs{ fc, ios_, std::string{s + pre, len - pre}}; - fail_parser p(fc); - error_code ec; - parse(fs, sb, p, ec); + test_parser p(fc); + error_code ec = test::error::fail_error; + read(fs, b, p, ec); if(! ec) break; } BEAST_EXPECT(n < limit); for(n = 0; n < limit; ++n) { - streambuf sb; - sb.commit(buffer_copy( - sb.prepare(len), buffer(s, len))); + multi_buffer b; + b.commit(buffer_copy( + b.prepare(len), buffer(s, len))); test::fail_counter fc(n); test::fail_stream< test::string_istream> fs{fc, ios_, ""}; - fail_parser p(fc); - error_code ec; - async_parse(fs, sb, p, do_yield[ec]); + test_parser p(fc); + error_code ec = test::error::fail_error; + async_read(fs, b, p, do_yield[ec]); if(! ec) break; } @@ -138,29 +90,15 @@ public: for(n = 0; n < limit; ++n) { static std::size_t constexpr pre = 10; - streambuf sb; - sb.commit(buffer_copy( - sb.prepare(pre), buffer(s, pre))); + multi_buffer b; + b.commit(buffer_copy( + b.prepare(pre), buffer(s, pre))); test::fail_counter fc(n); test::fail_stream fs{ fc, ios_, std::string{s + pre, len - pre}}; - fail_parser p(fc); - error_code ec; - async_parse(fs, sb, p, do_yield[ec]); - if(! ec) - break; - } - BEAST_EXPECT(n < limit); - for(n = 0; n < limit; ++n) - { - streambuf sb; - sb.commit(buffer_copy( - sb.prepare(len), buffer(s, len))); - test::fail_counter fc{n}; - test::string_istream ss{ios_, s}; - parser_v1 p{fc}; - error_code ec; - parse(ss, sb, p, ec); + test_parser p(fc); + error_code ec = test::error::fail_error; + async_read(fs, b, p, do_yield[ec]); if(! ec) break; } @@ -171,10 +109,10 @@ public: { try { - streambuf sb; + multi_buffer b; test::string_istream ss(ios_, "GET / X"); - parser_v1 p; - parse(ss, sb, p); + request_parser p; + read(ss, b, p); fail(); } catch(std::exception const&) @@ -183,6 +121,52 @@ public: } } + void + testBufferOverflow() + { + { + test::pipe p{ios_}; + ostream(p.server.buffer) << + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "User-Agent: test\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "10\r\n" + "****************\r\n" + "0\r\n\r\n"; + static_buffer_n<1024> b; + request req; + try + { + read(p.server, b, req); + pass(); + } + catch(std::exception const& e) + { + fail(e.what(), __FILE__, __LINE__); + } + } + { + test::pipe p{ios_}; + ostream(p.server.buffer) << + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "User-Agent: test\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "10\r\n" + "****************\r\n" + "0\r\n\r\n"; + error_code ec = test::error::fail_error; + static_buffer_n<10> b; + request req; + read(p.server, b, req, ec); + BEAST_EXPECTS(ec == error::buffer_overflow, + ec.message()); + } + } + void testFailures(yield_context do_yield) { char const* req[] = { @@ -245,52 +229,6 @@ public: failMatrix(res[i], do_yield); } - void testReadHeaders(yield_context do_yield) - { - static std::size_t constexpr limit = 100; - std::size_t n; - - for(n = 0; n < limit; ++n) - { - test::fail_stream fs{n, ios_, - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "User-Agent: test\r\n" - "Content-Length: 5\r\n" - "\r\n" - }; - request_header m; - try - { - streambuf sb; - read(fs, sb, m); - break; - } - catch(std::exception const&) - { - } - } - BEAST_EXPECT(n < limit); - - for(n = 0; n < limit; ++n) - { - test::fail_stream fs(n, ios_, - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "User-Agent: test\r\n" - "Content-Length: 0\r\n" - "\r\n" - ); - request_header m; - error_code ec; - streambuf sb; - async_read(fs, sb, m, do_yield[ec]); - if(! ec) - break; - } - BEAST_EXPECT(n < limit); - } - void testRead(yield_context do_yield) { static std::size_t constexpr limit = 100; @@ -305,11 +243,11 @@ public: "Content-Length: 0\r\n" "\r\n" ); - request m; + request m; try { - streambuf sb; - read(fs, sb, m); + multi_buffer b; + read(fs, b, m); break; } catch(std::exception const&) @@ -327,10 +265,10 @@ public: "Content-Length: 0\r\n" "\r\n" ); - request m; - error_code ec; - streambuf sb; - read(fs, sb, m, ec); + request m; + error_code ec = test::error::fail_error; + multi_buffer b; + read(fs, b, m, ec); if(! ec) break; } @@ -345,33 +283,34 @@ public: "Content-Length: 0\r\n" "\r\n" ); - request m; - error_code ec; - streambuf sb; - async_read(fs, sb, m, do_yield[ec]); + request m; + error_code ec = test::error::fail_error; + multi_buffer b; + async_read(fs, b, m, do_yield[ec]); if(! ec) break; } BEAST_EXPECT(n < limit); } - void testEof(yield_context do_yield) + void + testEof(yield_context do_yield) { { - streambuf sb; + multi_buffer b; test::string_istream ss(ios_, ""); - parser_v1 p; + request_parser p; error_code ec; - parse(ss, sb, p, ec); - BEAST_EXPECT(ec == boost::asio::error::eof); + read(ss, b, p, ec); + BEAST_EXPECT(ec == http::error::end_of_stream); } { - streambuf sb; + multi_buffer b; test::string_istream ss(ios_, ""); - parser_v1 p; + request_parser p; error_code ec; - async_parse(ss, sb, p, do_yield[ec]); - BEAST_EXPECT(ec == boost::asio::error::eof); + async_read(ss, b, p, do_yield[ec]); + BEAST_EXPECT(ec == http::error::end_of_stream); } } @@ -396,9 +335,9 @@ public: test::string_istream is{ios, "GET / HTTP/1.1\r\n\r\n"}; BEAST_EXPECT(handler::count() == 0); - streambuf sb; - message m; - async_read(is, sb, m, handler{}); + multi_buffer b; + request m; + async_read(is, b, m, handler{}); BEAST_EXPECT(handler::count() > 0); ios.stop(); BEAST_EXPECT(handler::count() > 0); @@ -415,25 +354,107 @@ public: test::string_istream is{ios, "GET / HTTP/1.1\r\n\r\n"}; BEAST_EXPECT(handler::count() == 0); - streambuf sb; - message m; - async_read(is, sb, m, handler{}); + multi_buffer b; + request m; + async_read(is, b, m, handler{}); BEAST_EXPECT(handler::count() > 0); } BEAST_EXPECT(handler::count() == 0); } } - void run() override + // https://github.com/vinniefalco/Beast/issues/430 + void + testRegression430() + { + test::pipe c{ios_}; + c.server.read_size(1); + ostream(c.server.buffer) << + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "Content-Type: application/octet-stream\r\n" + "\r\n" + "4\r\nabcd\r\n" + "0\r\n\r\n"; + error_code ec; + flat_buffer fb; + parser p; + read(c.server, fb, p, ec); + BEAST_EXPECTS(! ec, ec.message()); + } + + //-------------------------------------------------------------------------- + + template + void + readgrind(string_view s, Pred&& pred) + { + using boost::asio::buffer; + for(std::size_t n = 1; n < s.size() - 1; ++n) + { + Parser p; + error_code ec = test::error::fail_error; + flat_buffer b; + test::pipe c{ios_}; + ostream(c.server.buffer) << s; + c.server.read_size(n); + read(c.server, b, p, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + continue; + pred(p); + } + } + + void + testReadGrind() + { + readgrind>( + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "Content-Type: application/octet-stream\r\n" + "\r\n" + "4\r\nabcd\r\n" + "0\r\n\r\n" + ,[&](test_parser const& p) + { + BEAST_EXPECT(p.body == "abcd"); + }); + readgrind>( + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "Expect: Expires, MD5-Fingerprint\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "5\r\n" + "*****\r\n" + "2;a;b=1;c=\"2\"\r\n" + "--\r\n" + "0;d;e=3;f=\"4\"\r\n" + "Expires: never\r\n" + "MD5-Fingerprint: -\r\n" + "\r\n" + ,[&](test_parser const& p) + { + BEAST_EXPECT(p.body == "*****--"); + }); + } + + void + run() override { testThrow(); + testBufferOverflow(); - yield_to(&read_test::testFailures, this); - yield_to(&read_test::testReadHeaders, this); - yield_to(&read_test::testRead, this); - yield_to(&read_test::testEof, this); + yield_to([&](yield_context yield){ + testFailures(yield); }); + yield_to([&](yield_context yield){ + testRead(yield); }); + yield_to([&](yield_context yield){ + testEof(yield); }); testIoService(); + testRegression430(); + testReadGrind(); } }; diff --git a/test/http/reason.cpp b/test/http/reason.cpp deleted file mode 100644 index c5987d3590..0000000000 --- a/test/http/reason.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// -// 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) -// - -// Test that header file is self-contained. -#include - -#include - -namespace beast { -namespace http { - -class reason_test : public unit_test::suite -{ -public: - void run() override - { - for(int i = 1; i <= 999; ++i) - BEAST_EXPECT(reason_string(i) != nullptr); - } -}; - -BEAST_DEFINE_TESTSUITE(reason,http,beast); - -} // http -} // beast diff --git a/test/http/rfc7230.cpp b/test/http/rfc7230.cpp index 1e486bfa5f..dfa5b068f4 100644 --- a/test/http/rfc7230.cpp +++ b/test/http/rfc7230.cpp @@ -13,9 +13,10 @@ #include #include +#include + namespace beast { namespace http { -namespace test { class rfc7230_test : public beast::unit_test::suite { @@ -29,7 +30,7 @@ public: static std::string - str(boost::string_ref const& s) + str(string_view s) { return std::string(s.data(), s.size()); } @@ -62,18 +63,18 @@ public: BEAST_EXPECTS(got == s, fmt(got)); }; auto const cs = - [&](std::string const& s, std::string const& good) + [&](std::string const& s, std::string const& answer) { - ce(good); + ce(answer); auto const got = str(param_list{s}); ce(got); - BEAST_EXPECTS(got == good, fmt(got)); + BEAST_EXPECTS(got == answer, fmt(got)); }; auto const cq = - [&](std::string const& s, std::string const& good) + [&](std::string const& s, std::string const& answer) { auto const got = str(param_list{s}); - BEAST_EXPECTS(got == good, fmt(got)); + BEAST_EXPECTS(got == answer, fmt(got)); }; ce(""); @@ -135,9 +136,9 @@ public: BEAST_EXPECTS(got == good, fmt(got)); }; /* - ext-list = *( "," OWS ) ext *( OWS "," [ OWS ext ] ) - ext = token param-list - param-list = *( OWS ";" OWS param ) + ext-basic_parsed_list = *( "," OWS ) ext *( OWS "," [ OWS ext ] ) + ext = token param-basic_parsed_list + param-basic_parsed_list = *( OWS ";" OWS param ) param = token OWS "=" OWS ( token / quoted-string ) */ cs(",", ""); @@ -237,17 +238,120 @@ public: cs("x y", "x"); } + template + static + std::vector + to_vector(string_view in) + { + std::vector v; + detail::basic_parsed_list list{in}; + for(auto const& s : + detail::basic_parsed_list{in}) + v.emplace_back(s.data(), s.size()); + return v; + } + + template + void + validate(string_view in, + std::vector const& v) + { + BEAST_EXPECT(to_vector(in) == v); + } + + template + void + good(string_view in) + { + BEAST_EXPECT(validate_list( + detail::basic_parsed_list{in})); + } + + template + void + good(string_view in, + std::vector const& v) + { + BEAST_EXPECT(validate_list( + detail::basic_parsed_list{in})); + validate(in, v); + } + + template + void + bad(string_view in) + { + BEAST_EXPECT(! validate_list( + detail::basic_parsed_list{in})); + } + + void + testOptTokenList() + { + /* + #token = [ ( "," / token ) *( OWS "," [ OWS token ] ) ] + */ + using type = detail::opt_token_list_policy; + + good("", {}); + good(" ", {}); + good("\t", {}); + good(" \t", {}); + good(",", {}); + good(",,", {}); + good(", ,", {}); + good(",\t,", {}); + good(", \t,", {}); + good(", \t, ", {}); + good(", \t,\t", {}); + good(", \t, \t", {}); + + good("x", {"x"}); + good(" x", {"x"}); + good("x,,", {"x"}); + good("x, ,", {"x"}); + good("x,, ", {"x"}); + good("x,,,", {"x"}); + + good("x,y", {"x","y"}); + good("x ,y", {"x","y"}); + good("x\t,y", {"x","y"}); + good("x \t,y", {"x","y"}); + good(" x,y", {"x","y"}); + good(" x,y ", {"x","y"}); + good(",x,y", {"x","y"}); + good("x,y,", {"x","y"}); + good(",,x,y", {"x","y"}); + good(",x,,y", {"x","y"}); + good(",x,y,", {"x","y"}); + good("x ,, y", {"x","y"}); + good("x , ,y", {"x","y"}); + + good("x,y,z", {"x","y","z"}); + + bad("("); + bad("x("); + bad("(x"); + bad(",("); + bad("(,"); + bad("x,("); + bad("(,x"); + bad("x y"); + } + void run() { + testOptTokenList(); +#if 0 testParamList(); testExtList(); testTokenList(); +#endif } }; BEAST_DEFINE_TESTSUITE(rfc7230,http,beast); -} // test } // http } // beast diff --git a/test/http/serializer.cpp b/test/http/serializer.cpp new file mode 100644 index 0000000000..d39dbcb50e --- /dev/null +++ b/test/http/serializer.cpp @@ -0,0 +1,125 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include + +#include +#include + +namespace beast { +namespace http { + +class serializer_test : public beast::unit_test::suite +{ +public: + struct const_body + { + struct value_type{}; + + struct reader + { + using const_buffers_type = + boost::asio::const_buffers_1; + + template + reader(message const&); + + void + init(error_code& ec); + + boost::optional> + get(error_code&); + }; + }; + + struct mutable_body + { + struct value_type{}; + + struct reader + { + using const_buffers_type = + boost::asio::const_buffers_1; + + template + reader(message&); + + void + init(error_code& ec); + + boost::optional> + get(error_code&); + }; + }; + + BOOST_STATIC_ASSERT(std::is_const< serializer< + true, const_body>::value_type>::value); + + BOOST_STATIC_ASSERT(! std::is_const::value_type>::value); + + BOOST_STATIC_ASSERT(std::is_constructible< + serializer, + message &>::value); + + BOOST_STATIC_ASSERT(std::is_constructible< + serializer, + message const&>::value); + + BOOST_STATIC_ASSERT(std::is_constructible< + serializer, + message &>::value); + + BOOST_STATIC_ASSERT(! std::is_constructible< + serializer, + message const&>::value); + + struct lambda + { + std::size_t size; + + template + void + operator()(error_code&, + ConstBufferSequence const& buffers) + { + size = boost::asio::buffer_size(buffers); + } + }; + + void + testWriteLimit() + { + auto const limit = 30; + lambda visit; + error_code ec; + response res; + res.body.append(1000, '*'); + serializer sr{res}; + sr.limit(limit); + for(;;) + { + sr.next(ec, visit); + BEAST_EXPECT(visit.size <= limit); + sr.consume(visit.size); + if(sr.is_done()) + break; + } + } + + void + run() override + { + testWriteLimit(); + } +}; + +BEAST_DEFINE_TESTSUITE(serializer,http,beast); + +} // http +} // beast diff --git a/test/http/span_body.cpp b/test/http/span_body.cpp new file mode 100644 index 0000000000..3e9e3b178f --- /dev/null +++ b/test/http/span_body.cpp @@ -0,0 +1,76 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include + +#include +#include + +namespace beast { +namespace http { + +struct span_body_test + : public beast::unit_test::suite +{ + void + testSpanBody() + { + { + using B = span_body; + request req; + + BEAST_EXPECT(req.body.size() == 0); + BEAST_EXPECT(B::size(req.body) == 0); + + req.body = B::value_type("xyz", 3); + BEAST_EXPECT(req.body.size() == 3); + BEAST_EXPECT(B::size(req.body) == 3); + + B::reader r{req}; + error_code ec; + r.init(ec); + BEAST_EXPECTS(! ec, ec.message()); + auto const buf = r.get(ec); + BEAST_EXPECTS(! ec, ec.message()); + if(! BEAST_EXPECT(buf != boost::none)) + return; + BEAST_EXPECT(boost::asio::buffer_size(buf->first) == 3); + BEAST_EXPECT(! buf->second); + } + { + char buf[5]; + using B = span_body; + request req; + req.body = span{buf, sizeof(buf)}; + B::writer w{req}; + error_code ec; + w.init(boost::none, ec); + BEAST_EXPECTS(! ec, ec.message()); + w.put(boost::asio::const_buffers_1{ + "123", 3}, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(buf[0] == '1'); + BEAST_EXPECT(buf[1] == '2'); + BEAST_EXPECT(buf[2] == '3'); + w.put(boost::asio::const_buffers_1{ + "456", 3}, ec); + BEAST_EXPECTS(ec == error::buffer_overflow, ec.message()); + } + } + + void + run() override + { + testSpanBody(); + } +}; + +BEAST_DEFINE_TESTSUITE(span_body,http,beast); + +} // http +} // beast diff --git a/test/http/status.cpp b/test/http/status.cpp new file mode 100644 index 0000000000..69cdcbffa6 --- /dev/null +++ b/test/http/status.cpp @@ -0,0 +1,176 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include + +#include + +namespace beast { +namespace http { + +class status_test + : public beast::unit_test::suite +{ +public: + void + testStatus() + { + auto const check = [&](status s, int i, status_class sc) + { + BEAST_EXPECT(int_to_status(i) == s); + BEAST_EXPECT(to_status_class(i) == sc); + BEAST_EXPECT(to_status_class(int_to_status(i)) == sc); + }; + check(status::continue_ ,100, status_class::informational); + check(status::switching_protocols ,101, status_class::informational); + check(status::processing ,102, status_class::informational); + + check(status::ok ,200, status_class::successful); + check(status::created ,201, status_class::successful); + check(status::accepted ,202, status_class::successful); + check(status::non_authoritative_information ,203, status_class::successful); + check(status::no_content ,204, status_class::successful); + check(status::reset_content ,205, status_class::successful); + check(status::partial_content ,206, status_class::successful); + check(status::multi_status ,207, status_class::successful); + check(status::already_reported ,208, status_class::successful); + check(status::im_used ,226, status_class::successful); + + check(status::multiple_choices ,300, status_class::redirection); + check(status::moved_permanently ,301, status_class::redirection); + check(status::found ,302, status_class::redirection); + check(status::see_other ,303, status_class::redirection); + check(status::not_modified ,304, status_class::redirection); + check(status::use_proxy ,305, status_class::redirection); + check(status::temporary_redirect ,307, status_class::redirection); + check(status::permanent_redirect ,308, status_class::redirection); + + check(status::bad_request ,400, status_class::client_error); + check(status::unauthorized ,401, status_class::client_error); + check(status::payment_required ,402, status_class::client_error); + check(status::forbidden ,403, status_class::client_error); + check(status::not_found ,404, status_class::client_error); + check(status::method_not_allowed ,405, status_class::client_error); + check(status::not_acceptable ,406, status_class::client_error); + check(status::proxy_authentication_required ,407, status_class::client_error); + check(status::request_timeout ,408, status_class::client_error); + check(status::conflict ,409, status_class::client_error); + check(status::gone ,410, status_class::client_error); + check(status::length_required ,411, status_class::client_error); + check(status::precondition_failed ,412, status_class::client_error); + check(status::payload_too_large ,413, status_class::client_error); + check(status::uri_too_long ,414, status_class::client_error); + check(status::unsupported_media_type ,415, status_class::client_error); + check(status::range_not_satisfiable ,416, status_class::client_error); + check(status::expectation_failed ,417, status_class::client_error); + check(status::misdirected_request ,421, status_class::client_error); + check(status::unprocessable_entity ,422, status_class::client_error); + check(status::locked ,423, status_class::client_error); + check(status::failed_dependency ,424, status_class::client_error); + check(status::upgrade_required ,426, status_class::client_error); + check(status::precondition_required ,428, status_class::client_error); + check(status::too_many_requests ,429, status_class::client_error); + check(status::request_header_fields_too_large ,431, status_class::client_error); + check(status::connection_closed_without_response ,444, status_class::client_error); + check(status::unavailable_for_legal_reasons ,451, status_class::client_error); + check(status::client_closed_request ,499, status_class::client_error); + + check(status::internal_server_error ,500, status_class::server_error); + check(status::not_implemented ,501, status_class::server_error); + check(status::bad_gateway ,502, status_class::server_error); + check(status::service_unavailable ,503, status_class::server_error); + check(status::gateway_timeout ,504, status_class::server_error); + check(status::http_version_not_supported ,505, status_class::server_error); + check(status::variant_also_negotiates ,506, status_class::server_error); + check(status::insufficient_storage ,507, status_class::server_error); + check(status::loop_detected ,508, status_class::server_error); + check(status::not_extended ,510, status_class::server_error); + check(status::network_authentication_required ,511, status_class::server_error); + check(status::network_connect_timeout_error ,599, status_class::server_error); + + BEAST_EXPECT(to_status_class(1) == status_class::unknown); + BEAST_EXPECT(to_status_class(status::unknown) == status_class::unknown); + + auto const good = + [&](status v) + { + BEAST_EXPECT(obsolete_reason(v) != "Unknown Status"); + }; + good(status::continue_); + good(status::switching_protocols); + good(status::processing); + good(status::ok); + good(status::created); + good(status::accepted); + good(status::non_authoritative_information); + good(status::no_content); + good(status::reset_content); + good(status::partial_content); + good(status::multi_status); + good(status::already_reported); + good(status::im_used); + good(status::multiple_choices); + good(status::moved_permanently); + good(status::found); + good(status::see_other); + good(status::not_modified); + good(status::use_proxy); + good(status::temporary_redirect); + good(status::permanent_redirect); + good(status::bad_request); + good(status::unauthorized); + good(status::payment_required); + good(status::forbidden); + good(status::not_found); + good(status::method_not_allowed); + good(status::not_acceptable); + good(status::proxy_authentication_required); + good(status::request_timeout); + good(status::conflict); + good(status::gone); + good(status::length_required); + good(status::precondition_failed); + good(status::payload_too_large); + good(status::uri_too_long); + good(status::unsupported_media_type); + good(status::range_not_satisfiable); + good(status::expectation_failed); + good(status::misdirected_request); + good(status::unprocessable_entity); + good(status::locked); + good(status::failed_dependency); + good(status::upgrade_required); + good(status::precondition_required); + good(status::too_many_requests); + good(status::request_header_fields_too_large); + good(status::unavailable_for_legal_reasons); + good(status::internal_server_error); + good(status::not_implemented); + good(status::bad_gateway); + good(status::service_unavailable); + good(status::gateway_timeout); + good(status::http_version_not_supported); + good(status::variant_also_negotiates); + good(status::insufficient_storage); + good(status::loop_detected); + good(status::not_extended); + good(status::network_authentication_required); + } + + void + run() + { + testStatus(); + } +}; + +BEAST_DEFINE_TESTSUITE(status,http,beast); + +} // http +} // beast + diff --git a/test/http/string_body.cpp b/test/http/string_body.cpp index 4b84a939be..71e9dea274 100644 --- a/test/http/string_body.cpp +++ b/test/http/string_body.cpp @@ -7,3 +7,13 @@ // Test that header file is self-contained. #include + +namespace beast { +namespace http { + +BOOST_STATIC_ASSERT(is_body::value); +BOOST_STATIC_ASSERT(is_body_reader::value); +BOOST_STATIC_ASSERT(is_body_writer::value); + +} // http +} // beast diff --git a/test/http/test_parser.hpp b/test/http/test_parser.hpp new file mode 100644 index 0000000000..7a17ee9f86 --- /dev/null +++ b/test/http/test_parser.hpp @@ -0,0 +1,158 @@ +// +// 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_HTTP_TEST_PARSER_HPP +#define BEAST_HTTP_TEST_PARSER_HPP + +#include +#include +#include +#include + +namespace beast { +namespace http { + +template +class test_parser + : public basic_parser> +{ + test::fail_counter* fc_ = nullptr; + +public: + using mutable_buffers_type = + boost::asio::mutable_buffers_1; + + int status = 0; + int version = 0; + std::string method; + std::string path; + std::string reason; + std::string body; + int got_on_begin = 0; + int got_on_field = 0; + int got_on_header = 0; + int got_on_body = 0; + int got_content_length = 0; + int got_on_chunk = 0; + int got_on_complete = 0; + std::unordered_map< + std::string, std::string> fields; + + test_parser() = default; + + explicit + test_parser(test::fail_counter& fc) + : fc_(&fc) + { + } + + void + on_request(verb, string_view method_str_, + string_view path_, int version_, error_code& ec) + { + method = std::string( + method_str_.data(), method_str_.size()); + path = std::string( + path_.data(), path_.size()); + version = version_; + ++got_on_begin; + if(fc_) + fc_->fail(ec); + else + ec.assign(0, ec.category()); + } + + void + on_response(int code, + string_view reason_, + int version_, error_code& ec) + { + status = code; + reason = std::string( + reason_.data(), reason_.size()); + version = version_; + ++got_on_begin; + if(fc_) + fc_->fail(ec); + else + ec.assign(0, ec.category()); + } + + void + on_field(field, string_view name, + string_view value, error_code& ec) + { + ++got_on_field; + if(fc_) + fc_->fail(ec); + else + ec.assign(0, ec.category()); + fields[name.to_string()] = value.to_string(); + } + + void + on_header(error_code& ec) + { + ++got_on_header; + if(fc_) + fc_->fail(ec); + else + ec.assign(0, ec.category()); + } + + void + on_body(boost::optional< + std::uint64_t> const& content_length_, + error_code& ec) + { + ++got_on_body; + got_content_length = + static_cast(content_length_); + if(fc_) + fc_->fail(ec); + else + ec.assign(0, ec.category()); + } + + std::size_t + on_data(string_view s, + error_code& ec) + { + body.append(s.data(), s.size()); + if(fc_) + fc_->fail(ec); + else + ec.assign(0, ec.category()); + return s.size(); + } + + void + on_chunk(std::uint64_t, + string_view, error_code& ec) + { + ++got_on_chunk; + if(fc_) + fc_->fail(ec); + else + ec.assign(0, ec.category()); + } + + void + on_complete(error_code& ec) + { + ++got_on_complete; + if(fc_) + fc_->fail(ec); + else + ec.assign(0, ec.category()); + } +}; + +} // http +} // beast + +#endif diff --git a/test/http/type_traits.cpp b/test/http/type_traits.cpp new file mode 100644 index 0000000000..a461c5b0c2 --- /dev/null +++ b/test/http/type_traits.cpp @@ -0,0 +1,32 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include + +#include +#include + +namespace beast { +namespace http { + +BOOST_STATIC_ASSERT(! is_body_reader::value); + +BOOST_STATIC_ASSERT(is_body_reader::value); + +BOOST_STATIC_ASSERT(! is_body_writer::value); + +namespace { + +struct not_fields {}; + +} // (anonymous) + +BOOST_STATIC_ASSERT(! is_fields::value); + +} // http +} // beast diff --git a/test/core/handler_concepts.cpp b/test/http/vector_body.cpp similarity index 56% rename from test/core/handler_concepts.cpp rename to test/http/vector_body.cpp index ba3c3a5559..91252137ec 100644 --- a/test/core/handler_concepts.cpp +++ b/test/http/vector_body.cpp @@ -6,18 +6,14 @@ // // Test that header file is self-contained. -#include +#include namespace beast { +namespace http { -namespace { -struct T -{ - void operator()(int); -}; -} - -static_assert(is_CompletionHandler::value, ""); -static_assert(! is_CompletionHandler::value, ""); +BOOST_STATIC_ASSERT(is_body>::value); +BOOST_STATIC_ASSERT(is_body_reader>::value); +BOOST_STATIC_ASSERT(is_body_writer>::value); +} // http } // beast diff --git a/test/http/verb.cpp b/test/http/verb.cpp new file mode 100644 index 0000000000..ec90033d98 --- /dev/null +++ b/test/http/verb.cpp @@ -0,0 +1,128 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include + +#include + +namespace beast { +namespace http { + +class verb_test + : public beast::unit_test::suite +{ +public: + void + testVerb() + { + auto const good = + [&](verb v) + { + BEAST_EXPECT(string_to_verb(to_string(v)) == v); + }; + + good(verb::unknown); + + good(verb::delete_); + good(verb::get); + good(verb::head); + good(verb::post); + good(verb::put); + good(verb::connect); + good(verb::options); + good(verb::trace); + good(verb::copy); + good(verb::lock); + good(verb::mkcol); + good(verb::move); + good(verb::propfind); + good(verb::proppatch); + good(verb::search); + good(verb::unlock); + good(verb::bind); + good(verb::rebind); + good(verb::unbind); + good(verb::acl); + good(verb::report); + good(verb::mkactivity); + good(verb::checkout); + good(verb::merge); + good(verb::msearch); + good(verb::notify); + good(verb::subscribe); + good(verb::unsubscribe); + good(verb::patch); + good(verb::purge); + good(verb::mkcalendar); + good(verb::link); + good(verb::unlink); + + auto const bad = + [&](string_view s) + { + auto const v = string_to_verb(s); + BEAST_EXPECTS(v == verb::unknown, to_string(v)); + }; + + bad("AC_"); + bad("BIN_"); + bad("CHECKOU_"); + bad("CONNEC_"); + bad("COP_"); + bad("DELET_"); + bad("GE_"); + bad("HEA_"); + bad("LIN_"); + bad("LOC_"); + bad("M-SEARC_"); + bad("MERG_"); + bad("MKACTIVIT_"); + bad("MKCALENDA_"); + bad("MKCO_"); + bad("MOV_"); + bad("NOTIF_"); + bad("OPTION_"); + bad("PATC_"); + bad("POS_"); + bad("PROPFIN_"); + bad("PROPPATC_"); + bad("PURG_"); + bad("PU_"); + bad("REBIN_"); + bad("REPOR_"); + bad("SEARC_"); + bad("SUBSCRIB_"); + bad("TRAC_"); + bad("UNBIN_"); + bad("UNLIN_"); + bad("UNLOC_"); + bad("UNSUBSCRIB_"); + + try + { + to_string(static_cast(-1)); + fail("", __FILE__, __LINE__); + } + catch(std::exception const&) + { + pass(); + } + } + + void + run() + { + testVerb(); + } +}; + +BEAST_DEFINE_TESTSUITE(verb,http,beast); + +} // http +} // beast + diff --git a/test/http/write.cpp b/test/http/write.cpp index 79fe11ab55..bb0a7f638f 100644 --- a/test/http/write.cpp +++ b/test/http/write.cpp @@ -8,15 +8,16 @@ // Test that header file is self-contained. #include +#include #include #include -#include +#include #include -#include #include -#include -#include +#include #include +#include +#include #include #include #include @@ -36,60 +37,179 @@ public: { using value_type = std::string; - class writer + class reader { value_type const& body_; public: + using const_buffers_type = + boost::asio::const_buffers_1; + template explicit - writer(message const& msg) noexcept + reader(message const& msg) : body_(msg.body) { } void - init(error_code& ec) noexcept + init(error_code& ec) { - beast::detail::ignore_unused(ec); + ec.assign(0, ec.category()); } - template - bool - write(error_code&, WriteFunction&& wf) noexcept + boost::optional> + get(error_code& ec) { - wf(boost::asio::buffer(body_)); - return true; + ec.assign(0, ec.category()); + return {{const_buffers_type{ + body_.data(), body_.size()}, false}}; + } + }; + }; + + template< + bool isSplit, + bool isFinalEmpty + > + struct test_body + { + struct value_type + { + std::string s; + bool mutable read = false; + }; + + class reader + { + int step_ = 0; + value_type const& body_; + + public: + using const_buffers_type = + boost::asio::const_buffers_1; + + template + explicit + reader(message const& msg) + : body_(msg.body) + { + } + + void + init(error_code& ec) + { + ec.assign(0, ec.category()); + } + + boost::optional> + get(error_code& ec) + { + ec.assign(0, ec.category()); + body_.read = true; + return get( + std::integral_constant{}, + std::integral_constant{}); + } + + private: + boost::optional> + get( + std::false_type, // isSplit + std::false_type) // isFinalEmpty + { + using boost::asio::buffer; + if(body_.s.empty()) + return boost::none; + return {{buffer(body_.s.data(), body_.s.size()), false}}; + } + + boost::optional> + get( + std::false_type, // isSplit + std::true_type) // isFinalEmpty + { + using boost::asio::buffer; + if(body_.s.empty()) + return boost::none; + switch(step_) + { + case 0: + step_ = 1; + return {{buffer( + body_.s.data(), body_.s.size()), true}}; + default: + return boost::none; + } + } + + boost::optional> + get( + std::true_type, // isSplit + std::false_type) // isFinalEmpty + { + using boost::asio::buffer; + auto const n = (body_.s.size() + 1) / 2; + switch(step_) + { + case 0: + if(n == 0) + return boost::none; + step_ = 1; + return {{buffer(body_.s.data(), n), + body_.s.size() > 1}}; + default: + return {{buffer(body_.s.data() + n, + body_.s.size() - n), false}}; + } + } + + boost::optional> + get( + std::true_type, // isSplit + std::true_type) // isFinalEmpty + { + using boost::asio::buffer; + auto const n = (body_.s.size() + 1) / 2; + switch(step_) + { + case 0: + if(n == 0) + return boost::none; + step_ = body_.s.size() > 1 ? 1 : 2; + return {{buffer(body_.s.data(), n), true}}; + case 1: + BOOST_ASSERT(body_.s.size() > 1); + step_ = 2; + return {{buffer(body_.s.data() + n, + body_.s.size() - n), true}}; + default: + return boost::none; + } } }; }; struct fail_body { - class writer; + class reader; class value_type { - friend class writer; + friend class reader; std::string s_; test::fail_counter& fc_; - boost::asio::io_service& ios_; public: - value_type(test::fail_counter& fc, - boost::asio::io_service& ios) + explicit + value_type(test::fail_counter& fc) : fc_(fc) - , ios_(ios) { } - boost::asio::io_service& - get_io_service() const - { - return ios_; - } - value_type& operator=(std::string s) { @@ -98,101 +218,88 @@ public: } }; - class writer + class reader { std::size_t n_ = 0; value_type const& body_; public: + using const_buffers_type = + boost::asio::const_buffers_1; + template explicit - writer(message const& msg) noexcept + reader(message const& msg) : body_(msg.body) { } void - init(error_code& ec) noexcept + init(error_code& ec) { body_.fc_.fail(ec); } - template - bool - write(error_code& ec, WriteFunction&& wf) noexcept + boost::optional> + get(error_code& ec) { if(body_.fc_.fail(ec)) - return false; + return boost::none; if(n_ >= body_.s_.size()) - return true; - wf(boost::asio::buffer(body_.s_.data() + n_, 1)); - ++n_; - return n_ == body_.s_.size(); + return boost::none; + return {{const_buffers_type{ + body_.s_.data() + n_++, 1}, true}}; } }; }; + template + bool + equal_body(string_view sv, string_view body) + { + test::string_istream si{ + get_io_service(), sv.to_string()}; + message m; + multi_buffer b; + try + { + read(si, b, m); + return m.body == body; + } + catch(std::exception const& e) + { + log << "equal_body: " << e.what() << std::endl; + return false; + } + } + template std::string str(message const& m) { test::string_ostream ss(ios_); - write(ss, m); + error_code ec; + write(ss, m, ec); + if(ec && ec != error::end_of_stream) + BOOST_THROW_EXCEPTION(system_error{ec}); return ss.str; } - void - testAsyncWriteHeaders(yield_context do_yield) - { - { - header m; - m.version = 11; - m.method = "GET"; - m.url = "/"; - m.fields.insert("User-Agent", "test"); - error_code ec; - test::string_ostream ss{ios_}; - async_write(ss, m, do_yield[ec]); - if(BEAST_EXPECTS(! ec, ec.message())) - BEAST_EXPECT(ss.str == - "GET / HTTP/1.1\r\n" - "User-Agent: test\r\n" - "\r\n"); - } - { - header m; - m.version = 10; - m.status = 200; - m.reason = "OK"; - m.fields.insert("Server", "test"); - m.fields.insert("Content-Length", "5"); - error_code ec; - test::string_ostream ss{ios_}; - async_write(ss, m, do_yield[ec]); - if(BEAST_EXPECTS(! ec, ec.message())) - BEAST_EXPECT(ss.str == - "HTTP/1.0 200 OK\r\n" - "Server: test\r\n" - "Content-Length: 5\r\n" - "\r\n"); - } - } - void testAsyncWrite(yield_context do_yield) { { - message m; + response m; m.version = 10; - m.status = 200; - m.reason = "OK"; - m.fields.insert("Server", "test"); - m.fields.insert("Content-Length", "5"); + m.result(status::ok); + m.set(field::server, "test"); + m.set(field::content_length, "5"); m.body = "*****"; error_code ec; test::string_ostream ss{ios_}; async_write(ss, m, do_yield[ec]); - if(BEAST_EXPECTS(! ec, ec.message())) + if(BEAST_EXPECTS(ec == error::end_of_stream, ec.message())) BEAST_EXPECT(ss.str == "HTTP/1.0 200 OK\r\n" "Server: test\r\n" @@ -201,12 +308,11 @@ public: "*****"); } { - message m; + response m; m.version = 11; - m.status = 200; - m.reason = "OK"; - m.fields.insert("Server", "test"); - m.fields.insert("Transfer-Encoding", "chunked"); + m.result(status::ok); + m.set(field::server, "test"); + m.set(field::transfer_encoding, "chunked"); m.body = "*****"; error_code ec; test::string_ostream ss(ios_); @@ -234,14 +340,10 @@ public: test::fail_counter fc(n); test::fail_stream< test::string_ostream> fs(fc, ios_); - message m( - std::piecewise_construct, - std::forward_as_tuple(fc, ios_)); - m.method = "GET"; - m.url = "/"; - m.version = 10; - m.fields.insert("User-Agent", "test"); - m.fields.insert("Content-Length", "5"); + request m(verb::get, "/", 10, fc); + m.set(field::user_agent, "test"); + m.set(field::connection, "keep-alive"); + m.set(field::content_length, "5"); m.body = "*****"; try { @@ -249,6 +351,7 @@ public: BEAST_EXPECT(fs.next_layer().str == "GET / HTTP/1.0\r\n" "User-Agent: test\r\n" + "Connection: keep-alive\r\n" "Content-Length: 5\r\n" "\r\n" "*****" @@ -267,18 +370,13 @@ public: test::fail_counter fc(n); test::fail_stream< test::string_ostream> fs(fc, ios_); - message m( - std::piecewise_construct, - std::forward_as_tuple(fc, ios_)); - m.method = "GET"; - m.url = "/"; - m.version = 10; - m.fields.insert("User-Agent", "test"); - m.fields.insert("Transfer-Encoding", "chunked"); + request m{verb::get, "/", 10, fc}; + m.set(field::user_agent, "test"); + m.set(field::transfer_encoding, "chunked"); m.body = "*****"; - error_code ec; + error_code ec = test::error::fail_error; write(fs, m, ec); - if(ec == boost::asio::error::eof) + if(ec == error::end_of_stream) { BEAST_EXPECT(fs.next_layer().str == "GET / HTTP/1.0\r\n" @@ -302,18 +400,13 @@ public: test::fail_counter fc(n); test::fail_stream< test::string_ostream> fs(fc, ios_); - message m( - std::piecewise_construct, - std::forward_as_tuple(fc, ios_)); - m.method = "GET"; - m.url = "/"; - m.version = 10; - m.fields.insert("User-Agent", "test"); - m.fields.insert("Transfer-Encoding", "chunked"); + request m{verb::get, "/", 10, fc}; + m.set(field::user_agent, "test"); + m.set(field::transfer_encoding, "chunked"); m.body = "*****"; - error_code ec; + error_code ec = test::error::fail_error; async_write(fs, m, do_yield[ec]); - if(ec == boost::asio::error::eof) + if(ec == error::end_of_stream) { BEAST_EXPECT(fs.next_layer().str == "GET / HTTP/1.0\r\n" @@ -337,22 +430,19 @@ public: test::fail_counter fc(n); test::fail_stream< test::string_ostream> fs(fc, ios_); - message m( - std::piecewise_construct, - std::forward_as_tuple(fc, ios_)); - m.method = "GET"; - m.url = "/"; - m.version = 10; - m.fields.insert("User-Agent", "test"); - m.fields.insert("Content-Length", "5"); + request m{verb::get, "/", 10, fc}; + m.set(field::user_agent, "test"); + m.set(field::connection, "keep-alive"); + m.set(field::content_length, "5"); m.body = "*****"; - error_code ec; + error_code ec = test::error::fail_error; write(fs, m, ec); if(! ec) { BEAST_EXPECT(fs.next_layer().str == "GET / HTTP/1.0\r\n" "User-Agent: test\r\n" + "Connection: keep-alive\r\n" "Content-Length: 5\r\n" "\r\n" "*****" @@ -367,22 +457,19 @@ public: test::fail_counter fc(n); test::fail_stream< test::string_ostream> fs(fc, ios_); - message m( - std::piecewise_construct, - std::forward_as_tuple(fc, ios_)); - m.method = "GET"; - m.url = "/"; - m.version = 10; - m.fields.insert("User-Agent", "test"); - m.fields.insert("Content-Length", "5"); + request m{verb::get, "/", 10, fc}; + m.set(field::user_agent, "test"); + m.set(field::connection, "keep-alive"); + m.set(field::content_length, "5"); m.body = "*****"; - error_code ec; + error_code ec = test::error::fail_error; async_write(fs, m, do_yield[ec]); if(! ec) { BEAST_EXPECT(fs.next_layer().str == "GET / HTTP/1.0\r\n" "User-Agent: test\r\n" + "Connection: keep-alive\r\n" "Content-Length: 5\r\n" "\r\n" "*****" @@ -398,13 +485,13 @@ public: { // auto content-length HTTP/1.0 { - message m; - m.method = "GET"; - m.url = "/"; + request m; + m.method(verb::get); + m.target("/"); m.version = 10; - m.fields.insert("User-Agent", "test"); + m.set(field::user_agent, "test"); m.body = "*"; - prepare(m); + m.prepare_payload(); BEAST_EXPECT(str(m) == "GET / HTTP/1.0\r\n" "User-Agent: test\r\n" @@ -413,55 +500,19 @@ public: "*" ); } - // keep-alive HTTP/1.0 - { - message m; - m.method = "GET"; - m.url = "/"; - m.version = 10; - m.fields.insert("User-Agent", "test"); - m.body = "*"; - prepare(m, connection::keep_alive); - BEAST_EXPECT(str(m) == - "GET / HTTP/1.0\r\n" - "User-Agent: test\r\n" - "Content-Length: 1\r\n" - "Connection: keep-alive\r\n" - "\r\n" - "*" - ); - } - // upgrade HTTP/1.0 - { - message m; - m.method = "GET"; - m.url = "/"; - m.version = 10; - m.fields.insert("User-Agent", "test"); - m.body = "*"; - try - { - prepare(m, connection::upgrade); - fail(); - } - catch(std::exception const&) - { - pass(); - } - } // no content-length HTTP/1.0 { - message m; - m.method = "GET"; - m.url = "/"; + request m; + m.method(verb::get); + m.target("/"); m.version = 10; - m.fields.insert("User-Agent", "test"); + m.set(field::user_agent, "test"); m.body = "*"; - prepare(m); + m.prepare_payload(); test::string_ostream ss(ios_); error_code ec; write(ss, m, ec); - BEAST_EXPECT(ec == boost::asio::error::eof); + BEAST_EXPECT(ec == error::end_of_stream); BEAST_EXPECT(ss.str == "GET / HTTP/1.0\r\n" "User-Agent: test\r\n" @@ -471,13 +522,13 @@ public: } // auto content-length HTTP/1.1 { - message m; - m.method = "GET"; - m.url = "/"; + request m; + m.method(verb::get); + m.target("/"); m.version = 11; - m.fields.insert("User-Agent", "test"); + m.set(field::user_agent, "test"); m.body = "*"; - prepare(m); + m.prepare_payload(); BEAST_EXPECT(str(m) == "GET / HTTP/1.1\r\n" "User-Agent: test\r\n" @@ -486,52 +537,15 @@ public: "*" ); } - // close HTTP/1.1 - { - message m; - m.method = "GET"; - m.url = "/"; - m.version = 11; - m.fields.insert("User-Agent", "test"); - m.body = "*"; - prepare(m, connection::close); - test::string_ostream ss(ios_); - error_code ec; - write(ss, m, ec); - BEAST_EXPECT(ec == boost::asio::error::eof); - BEAST_EXPECT(ss.str == - "GET / HTTP/1.1\r\n" - "User-Agent: test\r\n" - "Content-Length: 1\r\n" - "Connection: close\r\n" - "\r\n" - "*" - ); - } - // upgrade HTTP/1.1 - { - message m; - m.method = "GET"; - m.url = "/"; - m.version = 11; - m.fields.insert("User-Agent", "test"); - prepare(m, connection::upgrade); - BEAST_EXPECT(str(m) == - "GET / HTTP/1.1\r\n" - "User-Agent: test\r\n" - "Connection: upgrade\r\n" - "\r\n" - ); - } // no content-length HTTP/1.1 { - message m; - m.method = "GET"; - m.url = "/"; + request m; + m.method(verb::get); + m.target("/"); m.version = 11; - m.fields.insert("User-Agent", "test"); + m.set(field::user_agent, "test"); m.body = "*"; - prepare(m); + m.prepare_payload(); test::string_ostream ss(ios_); error_code ec; write(ss, m, ec); @@ -550,65 +564,14 @@ public: void test_std_ostream() { // Conversion to std::string via operator<< - message m; - m.method = "GET"; - m.url = "/"; + request m; + m.method(verb::get); + m.target("/"); m.version = 11; - m.fields.insert("User-Agent", "test"); + m.set(field::user_agent, "test"); m.body = "*"; BEAST_EXPECT(boost::lexical_cast(m) == "GET / HTTP/1.1\r\nUser-Agent: test\r\n\r\n*"); - BEAST_EXPECT(boost::lexical_cast(m.base()) == - "GET / HTTP/1.1\r\nUser-Agent: test\r\n\r\n"); - // Cause exceptions in operator<< - { - std::stringstream ss; - ss.setstate(ss.rdstate() | - std::stringstream::failbit); - try - { - // header - ss << m.base(); - fail("", __FILE__, __LINE__); - } - catch(std::exception const&) - { - pass(); - } - try - { - // message - ss << m; - fail("", __FILE__, __LINE__); - } - catch(std::exception const&) - { - pass(); - } - } - } - - void testOstream() - { - message m; - m.method = "GET"; - m.url = "/"; - m.version = 11; - m.fields.insert("User-Agent", "test"); - m.body = "*"; - prepare(m); - std::stringstream ss; - ss.setstate(ss.rdstate() | - std::stringstream::failbit); - try - { - ss << m; - fail(); - } - catch(std::exception const&) - { - pass(); - } } // Ensure completion handlers are not leaked @@ -631,11 +594,11 @@ public: boost::asio::io_service ios; test::string_ostream os{ios}; BEAST_EXPECT(handler::count() == 0); - message m; - m.method = "GET"; + request m; + m.method(verb::get); m.version = 11; - m.url = "/"; - m.fields["Content-Length"] = "5"; + m.target("/"); + m.set("Content-Length", 5); m.body = "*****"; async_write(os, m, handler{}); BEAST_EXPECT(handler::count() > 0); @@ -653,11 +616,11 @@ public: boost::asio::io_service ios; test::string_ostream is{ios}; BEAST_EXPECT(handler::count() == 0); - message m; - m.method = "GET"; + request m; + m.method(verb::get); m.version = 11; - m.url = "/"; - m.fields["Content-Length"] = "5"; + m.target("/"); + m.set("Content-Length", 5); m.body = "*****"; async_write(is, m, handler{}); BEAST_EXPECT(handler::count() > 0); @@ -666,15 +629,224 @@ public: } } + template + void + do_write(Stream& stream, message< + isRequest, Body, Fields> const& m, error_code& ec, + Decorator const& decorator = Decorator{}) + { + serializer sr{m, decorator}; + for(;;) + { + stream.nwrite = 0; + write_some(stream, sr, ec); + if(ec) + return; + BEAST_EXPECT(stream.nwrite <= 1); + if(sr.is_done()) + break; + } + } + + template + void + do_async_write(Stream& stream, + message const& m, + error_code& ec, yield_context yield, + Decorator const& decorator = Decorator{}) + { + serializer sr{m, decorator}; + for(;;) + { + stream.nwrite = 0; + async_write_some(stream, sr, yield[ec]); + if(ec) + return; + BEAST_EXPECT(stream.nwrite <= 1); + if(sr.is_done()) + break; + } + } + + struct test_decorator + { + std::string s; + + template + string_view + operator()(ConstBufferSequence const& buffers) + { + s = ";x=" + std::to_string(boost::asio::buffer_size(buffers)); + return s; + } + + string_view + operator()(boost::asio::null_buffers) + { + return "Result: OK\r\n"; + } + }; + + template + void + testWriteStream(boost::asio::yield_context yield) + { + test::pipe p{ios_}; + p.client.write_size(3); + + response m0; + m0.version = 11; + m0.result(status::ok); + m0.reason("OK"); + m0.set(field::server, "test"); + m0.body.s = "Hello, world!\n"; + + { + std::string const result = + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "\r\n" + "Hello, world!\n"; + { + auto m = m0; + error_code ec; + do_write(p.client, m, ec); + BEAST_EXPECT(p.server.str() == result); + BEAST_EXPECT(equal_body( + p.server.str(), m.body.s)); + p.server.clear(); + } + { + auto m = m0; + error_code ec; + do_async_write(p.client, m, ec, yield); + BEAST_EXPECT(p.server.str() == result); + BEAST_EXPECT(equal_body( + p.server.str(), m.body.s)); + p.server.clear(); + } + { + auto m = m0; + error_code ec; + response_serializer sr{m}; + sr.split(true); + for(;;) + { + write_some(p.client, sr); + if(sr.is_header_done()) + break; + } + BEAST_EXPECT(! m.body.read); + p.server.clear(); + } + { + auto m = m0; + error_code ec; + response_serializer sr{m}; + sr.split(true); + for(;;) + { + async_write_some(p.client, sr, yield); + if(sr.is_header_done()) + break; + } + BEAST_EXPECT(! m.body.read); + p.server.clear(); + } + } + { + m0.set("Transfer-Encoding", "chunked"); + { + auto m = m0; + error_code ec; + do_write(p.client, m, ec); + BEAST_EXPECT(equal_body( + p.server.str(), m.body.s)); + p.server.clear(); + } + { + auto m = m0; + error_code ec; + do_write(p.client, m, ec, test_decorator{}); + BEAST_EXPECT(equal_body( + p.server.str(), m.body.s)); + p.server.clear(); + } + { + auto m = m0; + error_code ec; + do_async_write(p.client, m, ec, yield); + BEAST_EXPECT(equal_body( + p.server.str(), m.body.s)); + p.server.clear(); + } + { + auto m = m0; + error_code ec; + do_async_write(p.client, m, ec, yield, test_decorator{}); + BEAST_EXPECT(equal_body( + p.server.str(), m.body.s)); + p.server.clear(); + } + { + auto m = m0; + error_code ec; + test::string_ostream so{get_io_service(), 3}; + response_serializer sr{m}; + sr.split(true); + for(;;) + { + write_some(p.client, sr); + if(sr.is_header_done()) + break; + } + BEAST_EXPECT(! m.body.read); + p.server.clear(); + } + { + auto m = m0; + error_code ec; + response_serializer sr{m}; + sr.split(true); + for(;;) + { + async_write_some(p.client, sr, yield); + if(sr.is_header_done()) + break; + } + BEAST_EXPECT(! m.body.read); + p.server.clear(); + } + } + } + void run() override { - yield_to(&write_test::testAsyncWriteHeaders, this); - yield_to(&write_test::testAsyncWrite, this); - yield_to(&write_test::testFailures, this); + yield_to( + [&](yield_context yield) + { + testAsyncWrite(yield); + }); + yield_to( + [&](yield_context yield) + { + testFailures(yield); + }); testOutput(); test_std_ostream(); - testOstream(); testIoService(); + yield_to( + [&](yield_context yield) + { + testWriteStream>(yield); + testWriteStream>(yield); + testWriteStream>(yield); + testWriteStream>(yield); + }); } }; diff --git a/test/server/CMakeLists.txt b/test/server/CMakeLists.txt new file mode 100644 index 0000000000..fe7cbbdeab --- /dev/null +++ b/test/server/CMakeLists.txt @@ -0,0 +1,40 @@ +# Part of Beast + +GroupSources(example/server-framework framework) +GroupSources(example/common common) +GroupSources(extras/beast extras) +GroupSources(include/beast beast) + +GroupSources(test/server "/") + +add_executable (server-test + ${BEAST_INCLUDES} + ${COMMON_INCLUDES} + ${SERVER_INCLUDES} + ../../extras/beast/unit_test/main.cpp + file_service.cpp + framework.cpp + http_async_port.cpp + http_base.cpp + http_sync_port.cpp + https_ports.cpp + multi_port.cpp + server.cpp + service_list.cpp + ssl_certificate + tests.cpp + ws_async_port.cpp + ws_sync_port.cpp + ws_upgrade_service.cpp + wss_ports.cpp +) + +target_link_libraries(server-test + Beast + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ) + +if (OPENSSL_FOUND) + target_link_libraries (server-test ${OPENSSL_LIBRARIES}) +endif() diff --git a/test/server/Jamfile b/test/server/Jamfile new file mode 100644 index 0000000000..2011716038 --- /dev/null +++ b/test/server/Jamfile @@ -0,0 +1,28 @@ +# +# 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) +# + +unit-test server-test : + ../../extras/beast/unit_test/main.cpp + file_service.cpp + framework.cpp + http_async_port.cpp + http_base.cpp + http_sync_port.cpp + https_ports.cpp + multi_port.cpp + server.cpp + service_list.cpp + ssl_certificate.cpp + tests.cpp + ws_async_port.cpp + ws_sync_port.cpp + ws_upgrade_service.cpp + wss_ports.cpp + : + coverage:no + ubasan:no + ; diff --git a/test/server/file_service.cpp b/test/server/file_service.cpp new file mode 100644 index 0000000000..334c60b23e --- /dev/null +++ b/test/server/file_service.cpp @@ -0,0 +1,10 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include "../../example/server-framework/file_service.hpp" + diff --git a/test/server/framework.cpp b/test/server/framework.cpp new file mode 100644 index 0000000000..ac99542706 --- /dev/null +++ b/test/server/framework.cpp @@ -0,0 +1,10 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include "../../example/server-framework/framework.hpp" + diff --git a/test/server/http_async_port.cpp b/test/server/http_async_port.cpp new file mode 100644 index 0000000000..dfed5a0a75 --- /dev/null +++ b/test/server/http_async_port.cpp @@ -0,0 +1,10 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include "../../example/server-framework/http_async_port.hpp" + diff --git a/test/server/http_base.cpp b/test/server/http_base.cpp new file mode 100644 index 0000000000..b27661d179 --- /dev/null +++ b/test/server/http_base.cpp @@ -0,0 +1,10 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include "../../example/server-framework/http_base.hpp" + diff --git a/test/server/http_sync_port.cpp b/test/server/http_sync_port.cpp new file mode 100644 index 0000000000..4625ad94ed --- /dev/null +++ b/test/server/http_sync_port.cpp @@ -0,0 +1,10 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include "../../example/server-framework/http_sync_port.hpp" + diff --git a/test/server/https_ports.cpp b/test/server/https_ports.cpp new file mode 100644 index 0000000000..f8414cfca1 --- /dev/null +++ b/test/server/https_ports.cpp @@ -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) +// + +#if BEAST_USE_OPENSSL + +// Test that header file is self-contained. +#include "../../example/server-framework/https_ports.hpp" + +#endif diff --git a/test/server/multi_port.cpp b/test/server/multi_port.cpp new file mode 100644 index 0000000000..a86ee0f3c3 --- /dev/null +++ b/test/server/multi_port.cpp @@ -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) +// + +#if BEAST_USE_OPENSSL + +// Test that header file is self-contained. +#include "../../example/server-framework/multi_port.hpp" + +#endif + diff --git a/test/server/server.cpp b/test/server/server.cpp new file mode 100644 index 0000000000..0920a34482 --- /dev/null +++ b/test/server/server.cpp @@ -0,0 +1,10 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include "../../example/server-framework/server.hpp" + diff --git a/test/server/service_list.cpp b/test/server/service_list.cpp new file mode 100644 index 0000000000..dd1569f8ad --- /dev/null +++ b/test/server/service_list.cpp @@ -0,0 +1,10 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include "../../example/server-framework/service_list.hpp" + diff --git a/test/server/ssl_certificate.cpp b/test/server/ssl_certificate.cpp new file mode 100644 index 0000000000..19b0baeee1 --- /dev/null +++ b/test/server/ssl_certificate.cpp @@ -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) +// + +#if BEAST_USE_OPENSSL + +// Test that header file is self-contained. +#include "../../example/server-framework/ssl_certificate.hpp" + +#endif diff --git a/test/server/tests.cpp b/test/server/tests.cpp new file mode 100644 index 0000000000..efd4b456e4 --- /dev/null +++ b/test/server/tests.cpp @@ -0,0 +1,328 @@ +// +// 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 "example/server-framework/http_sync_port.hpp" +#include "example/server-framework/http_async_port.hpp" +#include "example/server-framework/ws_sync_port.hpp" +#include "example/server-framework/ws_async_port.hpp" +#include "example/server-framework/ws_upgrade_service.hpp" + +#if BEAST_USE_OPENSSL +# include "example/server-framework/https_ports.hpp" +# include "example/server-framework/multi_port.hpp" +# include "example/server-framework/ssl_certificate.hpp" +# include "example/server-framework/wss_ports.hpp" +#endif + +#include +#include +#include +#include + +namespace beast { +namespace websocket { + +class server_test + : public beast::unit_test::suite + , public test::enable_yield_to +{ +public: + static unsigned short constexpr port_num = 6000; + + class set_ws_options + { + beast::websocket::permessage_deflate pmd_; + + public: + set_ws_options(beast::websocket::permessage_deflate const& pmd) + : pmd_(pmd) + { + } + + template + void + operator()(beast::websocket::stream& ws) const + { + ws.auto_fragment(false); + ws.set_option(pmd_); + ws.read_message_max(64 * 1024 * 1024); + } + }; + + set_ws_options + get_ws_options() + { + beast::websocket::permessage_deflate pmd; + pmd.client_enable = true; + pmd.server_enable = true; + pmd.compLevel = 3; + return set_ws_options{pmd}; + } + + template + void + doOptions(Stream& stream, error_code& ec) + { + beast::http::request req; + req.version = 11; + req.method(beast::http::verb::options); + req.target("*"); + req.set(beast::http::field::user_agent, "test"); + req.set(beast::http::field::connection, "close"); + + beast::http::write(stream, req, ec); + if(! BEAST_EXPECTS( + ec == beast::http::error::end_of_stream, + ec.message())) + return; + + beast::flat_buffer b; + beast::http::response res; + beast::http::read(stream, b, res, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + } + + template + void + doHello(stream& ws, error_code& ec) + { + ws.handshake("localhost", "/", ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + ws.write(boost::asio::buffer(std::string("Hello, world!")), ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + beast::multi_buffer b; + ws.read(b, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + ws.close(beast::websocket::close_code::normal, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + // VFALCO Verify the buffer's contents + drain_buffer drain; + for(;;) + { + ws.read(drain, ec); + if(ec == beast::websocket::error::closed) + break; + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + } + } + + void + httpClient(framework::endpoint_type const& ep) + { + error_code ec; + boost::asio::ip::tcp::socket con{ios_}; + con.connect(ep, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + doOptions(con, ec); + } + + void + wsClient(framework::endpoint_type const& ep) + { + error_code ec; + stream ws{ios_}; + ws.next_layer().connect(ep, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + doHello(ws, ec); + } + + void + testPlain() + { + using namespace framework; + + // ws sync + { + error_code ec; + server instance; + auto const ep1 = endpoint_type{ + address_type::from_string("127.0.0.1"), port_num}; + auto const wsp = instance.make_port( + ec, ep1, instance, log, get_ws_options()); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + auto const ep2 = endpoint_type{ + address_type::from_string("127.0.0.1"), + static_cast(port_num + 1)}; + auto const sp = instance.make_port< + http_sync_port>>( + ec, ep2, instance, log); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + sp->template init<0>(ec, *wsp); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + + wsClient(ep1); + wsClient(ep2); + + httpClient(ep2); + } + + // ws async + { + error_code ec; + server instance; + auto const ep1 = endpoint_type{ + address_type::from_string("127.0.0.1"), port_num}; + auto const wsp = instance.make_port( + ec, ep1, instance, log, get_ws_options()); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + auto const ep2 = endpoint_type{ + address_type::from_string("127.0.0.1"), + static_cast(port_num + 1)}; + auto const sp = instance.make_port< + http_async_port>>( + ec, ep2, instance, log); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + sp->template init<0>(ec, *wsp); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + + wsClient(ep1); + wsClient(ep2); + + httpClient(ep2); + } + } + +#if BEAST_USE_OPENSSL + // + // OpenSSL enabled ports + // + + void + httpsClient(framework::endpoint_type const& ep, + boost::asio::ssl::context& ctx) + { + error_code ec; + boost::asio::ssl::stream con{ios_, ctx}; + con.next_layer().connect(ep, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + con.handshake( + boost::asio::ssl::stream_base::client, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + doOptions(con, ec); + if(ec) + return; + con.shutdown(ec); + // VFALCO No idea why we get eof in the normal case + if(ec == boost::asio::error::eof) + ec.assign(0, ec.category()); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + } + + void + wssClient(framework::endpoint_type const& ep, + boost::asio::ssl::context& ctx) + { + error_code ec; + stream> wss{ios_, ctx}; + wss.next_layer().next_layer().connect(ep, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + wss.next_layer().handshake( + boost::asio::ssl::stream_base::client, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + doHello(wss, ec); + } + + void + testSSL() + { + using namespace framework; + + ssl_certificate cert; + + // wss sync + { + error_code ec; + server instance; + auto const ep1 = endpoint_type{ + address_type::from_string("127.0.0.1"), port_num}; + auto const wsp = instance.make_port( + ec, ep1, instance, log, cert.get(), get_ws_options()); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + auto const ep2 = endpoint_type{ + address_type::from_string("127.0.0.1"), + static_cast(port_num + 1)}; + auto const sp = instance.make_port< + https_sync_port>>( + ec, ep2, instance, log, cert.get()); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + sp->template init<0>(ec, *wsp); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + + wssClient(ep1, cert.get()); + wssClient(ep2, cert.get()); + + httpsClient(ep2, cert.get()); + } + + // wss async + { + error_code ec; + server instance; + auto const ep1 = endpoint_type{ + address_type::from_string("127.0.0.1"), port_num}; + auto const wsp = instance.make_port( + ec, ep1, instance, log, cert.get(), get_ws_options()); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + auto const ep2 = endpoint_type{ + address_type::from_string("127.0.0.1"), + static_cast(port_num + 1)}; + auto const sp = instance.make_port< + https_async_port>>( + ec, ep2, instance, log, cert.get()); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + sp->template init<0>(ec, *wsp); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + + wssClient(ep1, cert.get()); + wssClient(ep2, cert.get()); + + httpsClient(ep2, cert.get()); + } + } +#endif + + void + run() override + { + testPlain(); + + #if BEAST_USE_OPENSSL + testSSL(); + #endif + } +}; + +BEAST_DEFINE_TESTSUITE(server,websocket,beast); + +} // websocket +} // beast + diff --git a/test/server/ws_async_port.cpp b/test/server/ws_async_port.cpp new file mode 100644 index 0000000000..e826d97c1d --- /dev/null +++ b/test/server/ws_async_port.cpp @@ -0,0 +1,10 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include "../../example/server-framework/ws_async_port.hpp" + diff --git a/test/server/ws_sync_port.cpp b/test/server/ws_sync_port.cpp new file mode 100644 index 0000000000..8bba84d8a3 --- /dev/null +++ b/test/server/ws_sync_port.cpp @@ -0,0 +1,10 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include "../../example/server-framework/ws_sync_port.hpp" + diff --git a/test/server/ws_upgrade_service.cpp b/test/server/ws_upgrade_service.cpp new file mode 100644 index 0000000000..dcabff756a --- /dev/null +++ b/test/server/ws_upgrade_service.cpp @@ -0,0 +1,10 @@ +// +// 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) +// + +// Test that header file is self-contained. +#include "../../example/server-framework/ws_upgrade_service.hpp" + diff --git a/test/server/wss_ports.cpp b/test/server/wss_ports.cpp new file mode 100644 index 0000000000..6307f532c6 --- /dev/null +++ b/test/server/wss_ports.cpp @@ -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) +// + +#if BEAST_USE_OPENSSL + +// Test that header file is self-contained. +#include "../../example/server-framework/wss_ports.hpp" + +#endif diff --git a/test/websocket/CMakeLists.txt b/test/websocket/CMakeLists.txt index 5d2856edb3..2807477d34 100644 --- a/test/websocket/CMakeLists.txt +++ b/test/websocket/CMakeLists.txt @@ -2,14 +2,18 @@ GroupSources(extras/beast extras) GroupSources(include/beast beast) + GroupSources(test/websocket "/") +#include_directories(../../example/) + add_executable (websocket-tests ${BEAST_INCLUDES} ${EXTRAS_INCLUDES} ../../extras/beast/unit_test/main.cpp websocket_async_echo_server.hpp websocket_sync_echo_server.hpp + doc_snippets.cpp error.cpp option.cpp rfc6455.cpp @@ -20,12 +24,15 @@ add_executable (websocket-tests utf8_checker.cpp ) -if (NOT WIN32) - target_link_libraries(websocket-tests ${Boost_LIBRARIES} Threads::Threads) -else() - target_link_libraries(websocket-tests ${Boost_LIBRARIES}) -endif() +target_link_libraries(websocket-tests + Beast + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_COROUTINE_LIBRARY} + ${Boost_THREAD_LIBRARY} + ${Boost_CONTEXT_LIBRARY} + ) -if (MINGW) - set_target_properties(websocket-tests PROPERTIES COMPILE_FLAGS "-Wa,-mbig-obj -Og") +if (OPENSSL_FOUND) + target_link_libraries(websocket-tests ${OPENSSL_LIBRARIES}) endif() diff --git a/test/websocket/Jamfile b/test/websocket/Jamfile new file mode 100644 index 0000000000..4781f16aca --- /dev/null +++ b/test/websocket/Jamfile @@ -0,0 +1,19 @@ +# +# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +unit-test websocket-tests : + ../../extras/beast/unit_test/main.cpp + doc_snippets.cpp + error.cpp + option.cpp + rfc6455.cpp + stream.cpp + teardown.cpp + frame.cpp + mask.cpp + utf8_checker.cpp + ; diff --git a/test/websocket/doc_snippets.cpp b/test/websocket/doc_snippets.cpp new file mode 100644 index 0000000000..654c2ca3ad --- /dev/null +++ b/test/websocket/doc_snippets.cpp @@ -0,0 +1,282 @@ +// +// 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 +#include +#include +#include +#include +#include +#include +#include + +//[ws_snippet_1 +#include +using namespace beast::websocket; +//] + +using namespace beast; + +namespace doc_ws_snippets { + +void fxx() { + +boost::asio::io_service ios; +boost::asio::io_service::work work{ios}; +std::thread t{[&](){ ios.run(); }}; +error_code ec; +boost::asio::ip::tcp::socket sock{ios}; + +{ +//[ws_snippet_2 + stream ws{ios}; +//] +} + +{ +//[ws_snippet_3 + stream ws{std::move(sock)}; +//] +} + +{ +//[ws_snippet_4 + stream ws{sock}; +//] + +//[ws_snippet_5 + ws.next_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_send); +//] +} + +{ +//[ws_snippet_6 + std::string const host = "mywebapp.com"; + boost::asio::ip::tcp::resolver r{ios}; + stream ws{ios}; + boost::asio::connect(ws.next_layer(), r.resolve({host, "ws"})); +//] +} + +{ +//[ws_snippet_7 + boost::asio::ip::tcp::acceptor acceptor{ios}; + stream ws{acceptor.get_io_service()}; + acceptor.accept(ws.next_layer()); +//] +} + +{ + stream ws{ios}; +//[ws_snippet_8 + ws.handshake("localhost", "/"); +//] + +//[ws_snippet_9 + ws.handshake_ex("localhost", "/", + [](request_type& m) + { + m.insert(http::field::sec_websocket_protocol, "xmpp;ws-chat"); + }); +//] + +//[ws_snippet_10 + response_type res; + ws.handshake(res, "localhost", "/"); + if(! res.count(http::field::sec_websocket_protocol)) + throw std::invalid_argument("missing subprotocols"); +//] + +//[ws_snippet_11 + ws.accept(); +//] + +//[ws_snippet_12 + ws.accept_ex( + [](response_type& m) + { + m.insert(http::field::server, "MyServer"); + }); +//] +} + +{ +//[ws_snippet_13] + // Buffer required for reading HTTP messages + flat_buffer buffer; + + // Read the HTTP request ourselves + http::request req; + http::read(sock, buffer, req); + + // See if its a WebSocket upgrade request + if(websocket::is_upgrade(req)) + { + // Construct the stream, transferring ownership of the socket + stream ws{std::move(sock)}; + + // Accept the request from our message. Clients SHOULD NOT + // begin sending WebSocket frames until the server has + // provided a response, but just in case they did, we pass + // any leftovers in the buffer to the accept function. + // + ws.accept(req, buffer.data()); + } + else + { + // Its not a WebSocket upgrade, so + // handle it like a normal HTTP request. + } +//] +} + +{ + stream ws{ios}; +//[ws_snippet_14 + // Read into our buffer until we reach the end of the HTTP request. + // No parsing takes place here, we are just accumulating data. + boost::asio::streambuf buffer; + boost::asio::read_until(sock, buffer, "\r\n\r\n"); + + // Now accept the connection, using the buffered data. + ws.accept(buffer.data()); +//] +} +{ + stream ws{ios}; +//[ws_snippet_15 + multi_buffer buffer; + ws.read(buffer); + + ws.text(ws.got_text()); + ws.write(buffer.data()); + buffer.consume(buffer.size()); +//] +} + +{ + stream ws{ios}; +//[ws_snippet_16 + multi_buffer buffer; + for(;;) + if(ws.read_frame(buffer)) + break; + ws.binary(ws.got_binary()); + consuming_buffers cb{buffer.data()}; + for(;;) + { + using boost::asio::buffer_size; + if(buffer_size(cb) > 512) + { + ws.write_frame(false, buffer_prefix(512, cb)); + cb.consume(512); + } + else + { + ws.write_frame(true, cb); + break; + } + } +//] +} + +{ + stream ws{ios}; +//[ws_snippet_17 + ws.control_callback( + [](frame_type kind, string_view payload) + { + // Do something with the payload + boost::ignore_unused(kind, payload); + }); +//] + +//[ws_snippet_18 + ws.close(close_code::normal); +//] + +//[ws_snippet_19 + ws.auto_fragment(true); + ws.write_buffer_size(16384); +//] + +//[ws_snippet_20 + multi_buffer buffer; + ws.async_read(buffer, + [](error_code) + { + // Do something with the buffer + }); +//] +} + +} // fxx() + +// workaround for https://github.com/chriskohlhoff/asio/issues/112 +#ifdef BOOST_MSVC +//[ws_snippet_21 +void echo(stream& ws, + multi_buffer& buffer, boost::asio::yield_context yield) +{ + ws.async_read(buffer, yield); + std::future fut = + ws.async_write(buffer.data(), boost::asio::use_future); +} +//] +#endif + +} // doc_ws_snippets + +//------------------------------------------------------------------------------ + +#if BEAST_USE_OPENSSL + +//[wss_snippet_1 +#include +#include +//] + +namespace doc_wss_snippets { + +void fxx() { + +boost::asio::io_service ios; +boost::asio::io_service::work work{ios}; +std::thread t{[&](){ ios.run(); }}; +error_code ec; +boost::asio::ip::tcp::socket sock{ios}; + +{ +//[wss_snippet_2 + boost::asio::ssl::context ctx{boost::asio::ssl::context::sslv23}; + stream> wss{ios, ctx}; +//] +} + +{ +//[wss_snippet_3 + boost::asio::ip::tcp::endpoint ep; + boost::asio::ssl::context ctx{boost::asio::ssl::context::sslv23}; + stream> ws{ios, ctx}; + + // connect the underlying TCP/IP socket + ws.next_layer().next_layer().connect(ep); + + // perform SSL handshake + ws.next_layer().handshake(boost::asio::ssl::stream_base::client); + + // perform WebSocket handshake + ws.handshake("localhost", "/"); +//] +} + +} // fxx() + +} // doc_wss_snippets + +#endif + diff --git a/test/websocket/error.cpp b/test/websocket/error.cpp index 9c02468cfa..3032915d5c 100644 --- a/test/websocket/error.cpp +++ b/test/websocket/error.cpp @@ -34,17 +34,9 @@ public: void run() override { - check("websocket", error::closed); - check("websocket", error::failed); - check("websocket", error::handshake_failed); - check("websocket", error::keep_alive); - check("websocket", error::response_malformed); - check("websocket", error::response_failed); - check("websocket", error::response_denied); - check("websocket", error::request_malformed); - check("websocket", error::request_invalid); - check("websocket", error::request_denied); - check("websocket", error::general); + check("beast.websocket", error::closed); + check("beast.websocket", error::failed); + check("beast.websocket", error::handshake_failed); } }; diff --git a/test/websocket/frame.cpp b/test/websocket/frame.cpp index 13d2c51039..2dc21c4ecd 100644 --- a/test/websocket/frame.cpp +++ b/test/websocket/frame.cpp @@ -5,9 +5,11 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include #include -#include #include +#include +#include #include #include @@ -30,32 +32,34 @@ operator==(frame_header const& lhs, frame_header const& rhs) lhs.key == rhs.key; } -class frame_test : public beast::unit_test::suite +class frame_test + : public beast::unit_test::suite + , public test::enable_yield_to { public: void testCloseCodes() { - BEAST_EXPECT(! is_valid(0)); - BEAST_EXPECT(! is_valid(1)); - BEAST_EXPECT(! is_valid(999)); - BEAST_EXPECT(! is_valid(1004)); - BEAST_EXPECT(! is_valid(1005)); - BEAST_EXPECT(! is_valid(1006)); - BEAST_EXPECT(! is_valid(1016)); - BEAST_EXPECT(! is_valid(2000)); - BEAST_EXPECT(! is_valid(2999)); - BEAST_EXPECT(is_valid(1000)); - BEAST_EXPECT(is_valid(1002)); - BEAST_EXPECT(is_valid(3000)); - BEAST_EXPECT(is_valid(4000)); - BEAST_EXPECT(is_valid(5000)); + BEAST_EXPECT(! is_valid_close_code(0)); + BEAST_EXPECT(! is_valid_close_code(1)); + BEAST_EXPECT(! is_valid_close_code(999)); + BEAST_EXPECT(! is_valid_close_code(1004)); + BEAST_EXPECT(! is_valid_close_code(1005)); + BEAST_EXPECT(! is_valid_close_code(1006)); + BEAST_EXPECT(! is_valid_close_code(1016)); + BEAST_EXPECT(! is_valid_close_code(2000)); + BEAST_EXPECT(! is_valid_close_code(2999)); + BEAST_EXPECT(is_valid_close_code(1000)); + BEAST_EXPECT(is_valid_close_code(1002)); + BEAST_EXPECT(is_valid_close_code(3000)); + BEAST_EXPECT(is_valid_close_code(4000)); + BEAST_EXPECT(is_valid_close_code(5000)); } struct test_fh : frame_header { test_fh() { - op = opcode::text; + op = detail::opcode::text; fin = false; mask = false; rsv1 = false; @@ -68,29 +72,34 @@ public: void testFrameHeader() { + using stream_type = + beast::websocket::stream; + test::pipe p{ios_}; + // good frame fields { - role_type role = role_type::client; + stream_type::role_type role = + stream_type::role_type::client; auto check = [&](frame_header const& fh) { - fh_streambuf sb; - write(sb, fh); - close_code::value code; - stream_base stream; + fh_streambuf b; + write(b, fh); + close_code code; + stream_type stream{p.server}; stream.open(role); detail::frame_header fh1; auto const n = - stream.read_fh1(fh1, sb, code); + stream.read_fh1(fh1, b, code); if(! BEAST_EXPECT(! code)) return; - if(! BEAST_EXPECT(sb.size() == n)) + if(! BEAST_EXPECT(b.size() == n)) return; - stream.read_fh2(fh1, sb, code); + stream.read_fh2(fh1, b, code); if(! BEAST_EXPECT(! code)) return; - if(! BEAST_EXPECT(sb.size() == 0)) + if(! BEAST_EXPECT(b.size() == 0)) return; BEAST_EXPECT(fh1 == fh); }; @@ -99,7 +108,7 @@ public: check(fh); - role = role_type::server; + role = stream_type::role_type::server; fh.mask = true; fh.key = 1; check(fh); @@ -122,36 +131,36 @@ public: // bad frame fields { - role_type role = role_type::client; + stream_type::role_type role = stream_type::role_type::client; auto check = [&](frame_header const& fh) { - fh_streambuf sb; - write(sb, fh); - close_code::value code; - stream_base stream; + fh_streambuf b; + write(b, fh); + close_code code; + stream_type stream{p.server}; stream.open(role); - detail::frame_header fh1; + frame_header fh1; auto const n = - stream.read_fh1(fh1, sb, code); + stream.read_fh1(fh1, b, code); if(code) { pass(); return; } - if(! BEAST_EXPECT(sb.size() == n)) + if(! BEAST_EXPECT(b.size() == n)) return; - stream.read_fh2(fh1, sb, code); + stream.read_fh2(fh1, b, code); if(! BEAST_EXPECT(code)) return; - if(! BEAST_EXPECT(sb.size() == 0)) + if(! BEAST_EXPECT(b.size() == 0)) return; }; test_fh fh; - fh.op = opcode::close; + fh.op = detail::opcode::close; fh.fin = true; fh.len = 126; check(fh); @@ -169,11 +178,11 @@ public: check(fh); fh.rsv3 = false; - fh.op = opcode::rsv3; + fh.op = detail::opcode::rsv3; check(fh); - fh.op = opcode::text; + fh.op = detail::opcode::text; - fh.op = opcode::ping; + fh.op = detail::opcode::ping; fh.fin = false; check(fh); fh.fin = true; @@ -181,7 +190,7 @@ public: fh.mask = true; check(fh); - role = role_type::server; + role = stream_type::role_type::server; fh.mask = false; check(fh); } @@ -189,29 +198,32 @@ public: void bad(std::initializer_list bs) { + using stream_type = + beast::websocket::stream; using boost::asio::buffer; using boost::asio::buffer_copy; - static role_type constexpr role = role_type::client; + test::pipe p{ios_}; + static stream_type::role_type constexpr role = stream_type::role_type::client; std::vector v{bs}; - fh_streambuf sb; - sb.commit(buffer_copy(sb.prepare(v.size()), buffer(v))); - stream_base stream; + fh_streambuf b; + b.commit(buffer_copy(b.prepare(v.size()), buffer(v))); + stream_type stream{p.server}; stream.open(role); - close_code::value code; + close_code code; detail::frame_header fh; auto const n = - stream.read_fh1(fh, sb, code); + stream.read_fh1(fh, b, code); if(code) { pass(); return; } - if(! BEAST_EXPECT(sb.size() == n)) + if(! BEAST_EXPECT(b.size() == n)) return; - stream.read_fh2(fh, sb, code); + stream.read_fh2(fh, b, code); if(! BEAST_EXPECT(code)) return; - if(! BEAST_EXPECT(sb.size() == 0)) + if(! BEAST_EXPECT(b.size() == 0)) return; } diff --git a/test/websocket/rfc6455.cpp b/test/websocket/rfc6455.cpp index c34daf312f..169ba81000 100644 --- a/test/websocket/rfc6455.cpp +++ b/test/websocket/rfc6455.cpp @@ -7,3 +7,43 @@ // Test that header file is self-contained. #include + +#include + +namespace beast { +namespace websocket { + +class rfc6455_test + : public beast::unit_test::suite +{ +public: + void + test_is_upgrade() + { + http::header req; + req.version = 10; + BEAST_EXPECT(! is_upgrade(req)); + req.version = 11; + req.method(http::verb::post); + req.target("/"); + BEAST_EXPECT(! is_upgrade(req)); + req.method(http::verb::get); + req.insert("Connection", "upgrade"); + BEAST_EXPECT(! is_upgrade(req)); + req.insert("Upgrade", "websocket"); + BEAST_EXPECT(! is_upgrade(req)); + req.insert("Sec-WebSocket-Version", "13"); + BEAST_EXPECT(is_upgrade(req)); + } + + void + run() override + { + test_is_upgrade(); + } +}; + +BEAST_DEFINE_TESTSUITE(rfc6455,websocket,beast); + +} // websocket +} // beast diff --git a/test/websocket/stream.cpp b/test/websocket/stream.cpp index 47e0c833a0..02bab544a6 100644 --- a/test/websocket/stream.cpp +++ b/test/websocket/stream.cpp @@ -11,10 +11,12 @@ #include "websocket_async_echo_server.hpp" #include "websocket_sync_echo_server.hpp" -#include -#include +#include +#include #include #include +#include +#include #include #include #include @@ -36,6 +38,15 @@ public: using address_type = boost::asio::ip::address; using socket_type = boost::asio::ip::tcp::socket; + template + static + std::string + to_string(ConstBufferSequence const& bs) + { + return boost::lexical_cast< + std::string>(buffers(bs)); + } + struct con { stream ws; @@ -109,30 +120,432 @@ public: return false; } - struct test_decorator + struct SyncClient { - int& what; - - test_decorator(test_decorator const&) = default; - - test_decorator(int& what_) - : what(what_) + template + void + accept(stream& ws) const { - what = 0; + ws.accept(); } - template - void - operator()(http::header&) const + template + typename std::enable_if< + ! http::detail::is_header::value>::type + accept(stream& ws, + Buffers const& buffers) const { - what |= 1; + ws.accept(buffers); } - template + template void - operator()(http::header&) const + accept(stream& ws, + http::header const& req) const { - what |= 2; + ws.accept(req); + } + + template + void + accept(stream& ws, + http::header const& req, + Buffers const& buffers) const + { + ws.accept(req, buffers); + } + + template + void + accept_ex(stream& ws, + Decorator const& d) const + { + ws.accept_ex(d); + } + + template + typename std::enable_if< + ! http::detail::is_header::value>::type + accept_ex(stream& ws, + Buffers const& buffers, + Decorator const& d) const + { + ws.accept_ex(buffers, d); + } + + template + void + accept_ex(stream& ws, + http::header const& req, + Decorator const& d) const + { + ws.accept_ex(req, d); + } + + template + void + accept_ex(stream& ws, + http::header const& req, + Buffers const& buffers, + Decorator const& d) const + { + ws.accept_ex(req, buffers, d); + } + + template + void + handshake(stream& ws, + string_view uri, + string_view path) const + { + ws.handshake(uri, path); + } + + template + void + handshake(stream& ws, + response_type& res, + string_view uri, + string_view path) const + { + ws.handshake(res, uri, path); + } + + template + void + handshake_ex(stream& ws, + string_view uri, + string_view path, + Decorator const& d) const + { + ws.handshake_ex(uri, path, d); + } + + template + void + handshake_ex(stream& ws, + response_type& res, + string_view uri, + string_view path, + Decorator const& d) const + { + ws.handshake_ex(res, uri, path, d); + } + + template + void + ping(stream& ws, + ping_data const& payload) const + { + ws.ping(payload); + } + + template + void + pong(stream& ws, + ping_data const& payload) const + { + ws.pong(payload); + } + + template + void + close(stream& ws, + close_reason const& cr) const + { + ws.close(cr); + } + + template< + class NextLayer, class DynamicBuffer> + void + read(stream& ws, + DynamicBuffer& buffer) const + { + ws.read(buffer); + } + + template< + class NextLayer, class ConstBufferSequence> + void + write(stream& ws, + ConstBufferSequence const& buffers) const + { + ws.write(buffers); + } + + template< + class NextLayer, class ConstBufferSequence> + void + write_frame(stream& ws, bool fin, + ConstBufferSequence const& buffers) const + { + ws.write_frame(fin, buffers); + } + + template< + class NextLayer, class ConstBufferSequence> + void + write_raw(stream& ws, + ConstBufferSequence const& buffers) const + { + boost::asio::write( + ws.next_layer(), buffers); + } + }; + + class AsyncClient + { + yield_context& yield_; + + public: + explicit + AsyncClient(yield_context& yield) + : yield_(yield) + { + } + + template + void + accept(stream& ws) const + { + error_code ec; + ws.async_accept(yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + typename std::enable_if< + ! http::detail::is_header::value>::type + accept(stream& ws, + Buffers const& buffers) const + { + error_code ec; + ws.async_accept(buffers, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + accept(stream& ws, + http::header const& req) const + { + error_code ec; + ws.async_accept(req, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + accept(stream& ws, + http::header const& req, + Buffers const& buffers) const + { + error_code ec; + ws.async_accept(req, buffers, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + accept_ex(stream& ws, + Decorator const& d) const + { + error_code ec; + ws.async_accept_ex(d, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + typename std::enable_if< + ! http::detail::is_header::value>::type + accept_ex(stream& ws, + Buffers const& buffers, + Decorator const& d) const + { + error_code ec; + ws.async_accept_ex(buffers, d, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + accept_ex(stream& ws, + http::header const& req, + Decorator const& d) const + { + error_code ec; + ws.async_accept_ex(req, d, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + accept_ex(stream& ws, + http::header const& req, + Buffers const& buffers, + Decorator const& d) const + { + error_code ec; + ws.async_accept_ex( + req, buffers, d, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + handshake(stream& ws, + string_view uri, + string_view path) const + { + error_code ec; + ws.async_handshake( + uri, path, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + handshake(stream& ws, + response_type& res, + string_view uri, + string_view path) const + { + error_code ec; + ws.async_handshake( + res, uri, path, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + handshake_ex(stream& ws, + string_view uri, + string_view path, + Decorator const &d) const + { + error_code ec; + ws.async_handshake_ex( + uri, path, d, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + handshake_ex(stream& ws, + response_type& res, + string_view uri, + string_view path, + Decorator const &d) const + { + error_code ec; + ws.async_handshake_ex( + res, uri, path, d, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + ping(stream& ws, + ping_data const& payload) const + { + error_code ec; + ws.async_ping(payload, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + pong(stream& ws, + ping_data const& payload) const + { + error_code ec; + ws.async_pong(payload, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + close(stream& ws, + close_reason const& cr) const + { + error_code ec; + ws.async_close(cr, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template< + class NextLayer, class DynamicBuffer> + void + read(stream& ws, + DynamicBuffer& buffer) const + { + error_code ec; + ws.async_read(buffer, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template< + class NextLayer, class ConstBufferSequence> + void + write(stream& ws, + ConstBufferSequence const& buffers) const + { + error_code ec; + ws.async_write(buffers, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template< + class NextLayer, class ConstBufferSequence> + void + write_frame(stream& ws, bool fin, + ConstBufferSequence const& buffers) const + { + error_code ec; + ws.async_write_frame(fin, buffers, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template< + class NextLayer, class ConstBufferSequence> + void + write_raw(stream& ws, + ConstBufferSequence const& buffers) const + { + error_code ec; + boost::asio::async_write( + ws.next_layer(), buffers, yield_[ec]); + if(ec) + throw system_error{ec}; } }; @@ -140,24 +553,14 @@ public: testOptions() { stream ws(ios_); - ws.set_option(auto_fragment{true}); - ws.set_option(keep_alive{false}); - ws.set_option(write_buffer_size{2048}); - ws.set_option(message_type{opcode::text}); - ws.set_option(read_buffer_size{8192}); - ws.set_option(read_message_max{1 * 1024 * 1024}); + ws.auto_fragment(true); + ws.write_buffer_size(2048); + ws.binary(false); + ws.read_buffer_size(8192); + ws.read_message_max(1 * 1024 * 1024); try { - ws.set_option(write_buffer_size{7}); - fail(); - } - catch(std::exception const&) - { - pass(); - } - try - { - message_type{opcode::close}; + ws.write_buffer_size(7); fail(); } catch(std::exception const&) @@ -166,75 +569,419 @@ public: } } - void testAccept() + //-------------------------------------------------------------------------- + + class res_decorator { + bool& b_; + + public: + res_decorator(res_decorator const&) = default; + + explicit + res_decorator(bool& b) + : b_(b) { - static std::size_t constexpr limit = 100; - std::size_t n; - for(n = 0; n < limit; ++n) - { - // valid - http::request req; - req.method = "GET"; - req.url = "/"; - req.version = 11; - req.fields.insert("Host", "localhost"); - req.fields.insert("Upgrade", "websocket"); - req.fields.insert("Connection", "upgrade"); - req.fields.insert("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="); - req.fields.insert("Sec-WebSocket-Version", "13"); - stream> ws(n, ios_, ""); - try - { - ws.accept(req); - break; - } - catch(system_error const&) - { - } - } - BEAST_EXPECT(n < limit); } + + void + operator()(response_type&) const { - // valid - stream ws(ios_, - "GET / HTTP/1.1\r\n" - "Host: localhost:80\r\n" - "Upgrade: WebSocket\r\n" - "Connection: upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n" - ); + b_ = true; + } + }; + + template + void + testAccept(Client const& c) + { + static std::size_t constexpr limit = 200; + std::size_t n; + for(n = 0; n < limit; ++n) + { + test::fail_counter fc{n}; try { - ws.accept(); - pass(); + // request in stream + { + stream> ws{fc, ios_, + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + , 20}; + c.accept(ws); + // VFALCO validate contents of ws.next_layer().str? + } + { + stream> ws{fc, ios_, + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + , 20}; + bool called = false; + c.accept_ex(ws, res_decorator{called}); + BEAST_EXPECT(called); + } + // request in buffers + { + stream> ws{fc, ios_}; + c.accept(ws, sbuf( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + )); + } + { + stream> ws{fc, ios_}; + bool called = false; + c.accept_ex(ws, sbuf( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n"), + res_decorator{called}); + BEAST_EXPECT(called); + } + // request in buffers and stream + { + stream> ws{fc, ios_, + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + , 16}; + c.accept(ws, sbuf( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + )); + } + { + stream> ws{fc, ios_, + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + , 16}; + bool called = false; + c.accept_ex(ws, sbuf( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n"), + res_decorator{called}); + BEAST_EXPECT(called); + } + // request in message + { + request_type req; + req.method(http::verb::get); + req.target("/"); + req.version = 11; + req.insert("Host", "localhost"); + req.insert("Upgrade", "websocket"); + req.insert("Connection", "upgrade"); + req.insert("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="); + req.insert("Sec-WebSocket-Version", "13"); + stream> ws{fc, ios_}; + c.accept(ws, req); + } + { + request_type req; + req.method(http::verb::get); + req.target("/"); + req.version = 11; + req.insert("Host", "localhost"); + req.insert("Upgrade", "websocket"); + req.insert("Connection", "upgrade"); + req.insert("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="); + req.insert("Sec-WebSocket-Version", "13"); + stream> ws{fc, ios_}; + bool called = false; + c.accept_ex(ws, req, + res_decorator{called}); + BEAST_EXPECT(called); + } + // request in message, close frame in buffers + { + request_type req; + req.method(http::verb::get); + req.target("/"); + req.version = 11; + req.insert("Host", "localhost"); + req.insert("Upgrade", "websocket"); + req.insert("Connection", "upgrade"); + req.insert("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="); + req.insert("Sec-WebSocket-Version", "13"); + stream> ws{fc, ios_}; + c.accept(ws, req, + cbuf(0x88, 0x82, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x17)); + try + { + multi_buffer b; + c.read(ws, b); + fail("success", __FILE__, __LINE__); + } + catch(system_error const& e) + { + if(e.code() != websocket::error::closed) + throw; + } + } + { + request_type req; + req.method(http::verb::get); + req.target("/"); + req.version = 11; + req.insert("Host", "localhost"); + req.insert("Upgrade", "websocket"); + req.insert("Connection", "upgrade"); + req.insert("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="); + req.insert("Sec-WebSocket-Version", "13"); + stream> ws{fc, ios_}; + bool called = false; + c.accept_ex(ws, req, + cbuf(0x88, 0x82, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x17), + res_decorator{called}); + BEAST_EXPECT(called); + try + { + multi_buffer b; + c.read(ws, b); + fail("success", __FILE__, __LINE__); + } + catch(system_error const& e) + { + if(e.code() != websocket::error::closed) + throw; + } + } + // request in message, close frame in stream + { + request_type req; + req.method(http::verb::get); + req.target("/"); + req.version = 11; + req.insert("Host", "localhost"); + req.insert("Upgrade", "websocket"); + req.insert("Connection", "upgrade"); + req.insert("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="); + req.insert("Sec-WebSocket-Version", "13"); + stream> ws{fc, ios_, + "\x88\x82\xff\xff\xff\xff\xfc\x17"}; + c.accept(ws, req); + try + { + multi_buffer b; + c.read(ws, b); + fail("success", __FILE__, __LINE__); + } + catch(system_error const& e) + { + if(e.code() != websocket::error::closed) + throw; + } + } + // request in message, close frame in stream and buffers + { + request_type req; + req.method(http::verb::get); + req.target("/"); + req.version = 11; + req.insert("Host", "localhost"); + req.insert("Upgrade", "websocket"); + req.insert("Connection", "upgrade"); + req.insert("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="); + req.insert("Sec-WebSocket-Version", "13"); + stream> ws{fc, ios_, + "xff\xff\xfc\x17"}; + c.accept(ws, req, + cbuf(0x88, 0x82, 0xff, 0xff)); + try + { + multi_buffer b; + c.read(ws, b); + fail("success", __FILE__, __LINE__); + } + catch(system_error const& e) + { + if(e.code() != websocket::error::closed) + throw; + } + } + // failed handshake (missing Sec-WebSocket-Key) + { + stream> ws{fc, ios_, + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + , 20}; + try + { + c.accept(ws); + fail("success", __FILE__, __LINE__); + } + catch(system_error const& e) + { + if( e.code() != + websocket::error::handshake_failed && + e.code() != + boost::asio::error::eof) + throw; + } + } } catch(system_error const&) { - fail(); - } - } - { - // invalid - stream ws(ios_, - "GET / HTTP/1.0\r\n" - "\r\n" - ); - try - { - ws.accept(); - fail(); - } - catch(system_error const&) - { - pass(); + continue; } + break; } + BEAST_EXPECT(n < limit); } + void + testAccept() + { + testAccept(SyncClient{}); + yield_to( + [&](yield_context yield) + { + testAccept(AsyncClient{yield}); + }); + } + + //-------------------------------------------------------------------------- + + class req_decorator + { + bool& b_; + + public: + req_decorator(req_decorator const&) = default; + + explicit + req_decorator(bool& b) + : b_(b) + { + } + + void + operator()(request_type&) const + { + b_ = true; + } + }; + + template + void + testHandshake(endpoint_type const& ep, Client const& c) + { + static std::size_t constexpr limit = 200; + std::size_t n; + for(n = 199; n < limit; ++n) + { + test::fail_counter fc{n}; + try + { + // handshake + { + stream> ws{fc, ios_}; + ws.next_layer().next_layer().connect(ep); + c.handshake(ws, "localhost", "/"); + } + // handshake, response + { + stream> ws{fc, ios_}; + ws.next_layer().next_layer().connect(ep); + response_type res; + c.handshake(ws, res, "localhost", "/"); + // VFALCO validate res? + } + // handshake_ex + { + stream> ws{fc, ios_}; + ws.next_layer().next_layer().connect(ep); + bool called = false; + c.handshake_ex(ws, "localhost", "/", + req_decorator{called}); + BEAST_EXPECT(called); + } + // handshake_ex, response + { + stream> ws{fc, ios_}; + ws.next_layer().next_layer().connect(ep); + bool called = false; + response_type res; + c.handshake_ex(ws, res, "localhost", "/", + req_decorator{called}); + // VFALCO validate res? + BEAST_EXPECT(called); + } + } + catch(system_error const&) + { + continue; + } + break; + } + BEAST_EXPECT(n < limit); + } + + void + testHandshake() + { + error_code ec = test::error::fail_error; + ::websocket::async_echo_server server{nullptr, 1}; + auto const any = endpoint_type{ + address_type::from_string("127.0.0.1"), 0}; + server.open(any, ec); + BEAST_EXPECTS(! ec, ec.message()); + auto const ep = server.local_endpoint(); + testHandshake(ep, SyncClient{}); + yield_to( + [&](yield_context yield) + { + testHandshake(ep, AsyncClient{yield}); + }); + } + + //-------------------------------------------------------------------------- + void testBadHandshakes() { auto const check = @@ -244,7 +991,6 @@ public: { stream ws(ios_, s.substr(i, s.size() - i)); - ws.set_option(keep_alive{true}); try { ws.accept(boost::asio::buffer( @@ -258,7 +1004,7 @@ public: } }; // wrong version - check(error::handshake_failed, + check(http::error::end_of_stream, "GET / HTTP/1.0\r\n" "Host: localhost:80\r\n" "Upgrade: WebSocket\r\n" @@ -359,7 +1105,7 @@ public: } catch(system_error const& se) { - BEAST_EXPECT(se.code() == error::response_failed); + BEAST_EXPECT(se.code() == error::handshake_failed); } }; // wrong HTTP version @@ -424,31 +1170,14 @@ public: } void - testDecorator(endpoint_type const& ep) - { - error_code ec; - socket_type sock{ios_}; - sock.connect(ep, ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - stream ws{sock}; - int what; - ws.set_option(decorate(test_decorator{what})); - BEAST_EXPECT(what == 0); - ws.handshake("localhost", "/", ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - BEAST_EXPECT(what == 1); - } - - void testMask(endpoint_type const& ep, + testMask(endpoint_type const& ep, yield_context do_yield) { { std::vector v; for(char n = 0; n < 20; ++n) { - error_code ec; + error_code ec = test::error::fail_error; socket_type sock(ios_); sock.connect(ep, ec); if(! BEAST_EXPECTS(! ec, ec.message())) @@ -460,9 +1189,8 @@ public: ws.write(boost::asio::buffer(v), ec); if(! BEAST_EXPECTS(! ec, ec.message())) break; - opcode op; - streambuf db; - ws.read(op, db, ec); + multi_buffer db; + ws.read(db, ec); if(! BEAST_EXPECTS(! ec, ec.message())) break; BEAST_EXPECT(to_string(db.data()) == @@ -474,7 +1202,7 @@ public: std::vector v; for(char n = 0; n < 20; ++n) { - error_code ec; + error_code ec = test::error::fail_error; socket_type sock(ios_); sock.connect(ep, ec); if(! BEAST_EXPECTS(! ec, ec.message())) @@ -486,9 +1214,8 @@ public: ws.async_write(boost::asio::buffer(v), do_yield[ec]); if(! BEAST_EXPECTS(! ec, ec.message())) break; - opcode op; - streambuf db; - ws.async_read(op, db, do_yield[ec]); + multi_buffer db; + ws.async_read(db, do_yield[ec]); if(! BEAST_EXPECTS(! ec, ec.message())) break; BEAST_EXPECT(to_string(db.data()) == @@ -529,7 +1256,7 @@ public: } #if 0 - void testInvokable1(endpoint_type const& ep) + void testPausation1(endpoint_type const& ep) { boost::asio::io_service ios; stream ws(ios); @@ -537,7 +1264,7 @@ public: ws.handshake("localhost", "/"); // Make remote send a ping frame - ws.set_option(message_type(opcode::text)); + ws.binary(false); ws.write(buffer_cat(sbuf("PING"), sbuf("ping"))); std::size_t count = 0; @@ -551,10 +1278,9 @@ public: }); // Read - opcode op; - streambuf db; + multi_buffer db; ++count; - ws.async_read(op, db, + ws.async_read(db, [&](error_code ec) { --count; @@ -563,7 +1289,7 @@ public: while(! ws.wr_block_) ios.run_one(); // Write a text message, leaving - // the write_op suspended as invokable. + // the write_op suspended as pausation. ws.async_write(sbuf("Hello"), [&](error_code ec) { @@ -596,7 +1322,7 @@ public: } #endif - void testInvokable2(endpoint_type const& ep) + void testPausation2(endpoint_type const& ep) { boost::asio::io_service ios; stream ws(ios); @@ -604,15 +1330,14 @@ public: ws.handshake("localhost", "/"); // Make remote send a text message with bad utf8. - ws.set_option(message_type(opcode::binary)); + ws.binary(true); ws.write(buffer_cat(sbuf("TEXT"), cbuf(0x03, 0xea, 0xf0, 0x28, 0x8c, 0xbc))); - opcode op; - streambuf db; + multi_buffer db; std::size_t count = 0; // Read text message with bad utf8. // Causes a close to be sent, blocking writes. - ws.async_read(op, db, + ws.async_read(db, [&](error_code ec) { // Read should fail with protocol error @@ -620,7 +1345,7 @@ public: BEAST_EXPECTS( ec == error::failed, ec.message()); // Reads after failure are aborted - ws.async_read(op, db, + ws.async_read(db, [&](error_code ec) { ++count; @@ -633,7 +1358,7 @@ public: while(! ws.wr_block_) ios.run_one(); // Write a text message, leaving - // the write_op suspended as invokable. + // the write_op suspended as a pausation. ws.async_write(sbuf("Hello"), [&](error_code ec) { @@ -665,7 +1390,7 @@ public: ios.run(); } - void testInvokable3(endpoint_type const& ep) + void testPausation3(endpoint_type const& ep) { boost::asio::io_service ios; stream ws(ios); @@ -673,14 +1398,13 @@ public: ws.handshake("localhost", "/"); // Cause close to be received - ws.set_option(message_type(opcode::binary)); + ws.binary(true); ws.write(sbuf("CLOSE")); - opcode op; - streambuf db; + multi_buffer db; std::size_t count = 0; // Read a close frame. // Sends a close frame, blocking writes. - ws.async_read(op, db, + ws.async_read(db, [&](error_code ec) { // Read should complete with error::closed @@ -731,7 +1455,7 @@ public: ios.run(); } - void testInvokable4(endpoint_type const& ep) + void testPausation4(endpoint_type const& ep) { boost::asio::io_service ios; stream ws(ios); @@ -739,12 +1463,11 @@ public: ws.handshake("localhost", "/"); // Cause close to be received - ws.set_option(message_type(opcode::binary)); + ws.binary(true); ws.write(sbuf("CLOSE")); - opcode op; - streambuf db; + multi_buffer db; std::size_t count = 0; - ws.async_read(op, db, + ws.async_read(db, [&](error_code ec) { ++count; @@ -775,7 +1498,7 @@ public: } #if 0 - void testInvokable5(endpoint_type const& ep) + void testPausation5(endpoint_type const& ep) { boost::asio::io_service ios; stream ws(ios); @@ -792,9 +1515,8 @@ public: BEAST_EXPECT(! ec); }); }); - opcode op; - streambuf db; - ws.async_read(op, db, + multi_buffer db; + ws.async_read(db, [&](error_code ec) { BEAST_EXPECTS(ec == error::closed, ec.message()); @@ -824,9 +1546,8 @@ public: return; ws.write_frame(false, sbuf("u")); ws.write_frame(true, sbuf("v")); - streambuf sb; - opcode op; - ws.read(op, sb, ec); + multi_buffer b; + ws.read(b, ec); if(! BEAST_EXPECTS(! ec, ec.message())) return; } @@ -863,185 +1584,6 @@ public: } } - struct SyncClient - { - template - void - handshake(stream& ws, - boost::string_ref const& uri, - boost::string_ref const& path) const - { - ws.handshake(uri, path); - } - - template - void - ping(stream& ws, - ping_data const& payload) const - { - ws.ping(payload); - } - - template - void - pong(stream& ws, - ping_data const& payload) const - { - ws.pong(payload); - } - - template - void - close(stream& ws, - close_reason const& cr) const - { - ws.close(cr); - } - - template< - class NextLayer, class DynamicBuffer> - void - read(stream& ws, - opcode& op, DynamicBuffer& dynabuf) const - { - ws.read(op, dynabuf); - } - - template< - class NextLayer, class ConstBufferSequence> - void - write(stream& ws, - ConstBufferSequence const& buffers) const - { - ws.write(buffers); - } - - template< - class NextLayer, class ConstBufferSequence> - void - write_frame(stream& ws, bool fin, - ConstBufferSequence const& buffers) const - { - ws.write_frame(fin, buffers); - } - - template< - class NextLayer, class ConstBufferSequence> - void - write_raw(stream& ws, - ConstBufferSequence const& buffers) const - { - boost::asio::write( - ws.next_layer(), buffers); - } - }; - - class AsyncClient - { - yield_context& yield_; - - public: - explicit - AsyncClient(yield_context& yield) - : yield_(yield) - { - } - - template - void - handshake(stream& ws, - boost::string_ref const& uri, - boost::string_ref const& path) const - { - error_code ec; - ws.async_handshake(uri, path, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template - void - ping(stream& ws, - ping_data const& payload) const - { - error_code ec; - ws.async_ping(payload, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template - void - pong(stream& ws, - ping_data const& payload) const - { - error_code ec; - ws.async_pong(payload, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template - void - close(stream& ws, - close_reason const& cr) const - { - error_code ec; - ws.async_close(cr, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template< - class NextLayer, class DynamicBuffer> - void - read(stream& ws, - opcode& op, DynamicBuffer& dynabuf) const - { - error_code ec; - ws.async_read(op, dynabuf, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template< - class NextLayer, class ConstBufferSequence> - void - write(stream& ws, - ConstBufferSequence const& buffers) const - { - error_code ec; - ws.async_write(buffers, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template< - class NextLayer, class ConstBufferSequence> - void - write_frame(stream& ws, bool fin, - ConstBufferSequence const& buffers) const - { - error_code ec; - ws.async_write_frame(fin, buffers, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template< - class NextLayer, class ConstBufferSequence> - void - write_raw(stream& ws, - ConstBufferSequence const& buffers) const - { - error_code ec; - boost::asio::async_write( - ws.next_layer(), buffers, yield_[ec]); - if(ec) - throw system_error{ec}; - } - }; - struct abort_test { }; @@ -1063,9 +1605,8 @@ public: { try { - opcode op; - streambuf db; - c.read(ws, op, db); + multi_buffer db; + c.read(ws, db); fail(); throw abort_test{}; } @@ -1092,15 +1633,14 @@ public: c.handshake(ws, "localhost", "/"); // send message - ws.set_option(auto_fragment{false}); - ws.set_option(message_type(opcode::text)); + ws.auto_fragment(false); + ws.binary(false); c.write(ws, sbuf("Hello")); { // receive echoed message - opcode op; - streambuf db; - c.read(ws, op, db); - BEAST_EXPECT(op == opcode::text); + multi_buffer db; + c.read(ws, db); + BEAST_EXPECT(ws.got_text()); BEAST_EXPECT(to_string(db.data()) == "Hello"); } @@ -1116,116 +1656,116 @@ public: c.close(ws, {close_code::going_away, "Going away"}); restart(error::closed); + bool once; + // send ping and message - bool pong = false; - ws.set_option(ping_callback{ - [&](bool is_pong, ping_data const& payload) + once = false; + ws.control_callback( + [&](frame_type kind, string_view s) { - BEAST_EXPECT(is_pong); - BEAST_EXPECT(! pong); - pong = true; - BEAST_EXPECT(payload == ""); - }}); + BEAST_EXPECT(kind == frame_type::pong); + BEAST_EXPECT(! once); + once = true; + BEAST_EXPECT(s == ""); + }); c.ping(ws, ""); - ws.set_option(message_type(opcode::binary)); + ws.binary(true); c.write(ws, sbuf("Hello")); { // receive echoed message - opcode op; - streambuf db; - c.read(ws, op, db); - BEAST_EXPECT(pong == 1); - BEAST_EXPECT(op == opcode::binary); + multi_buffer db; + c.read(ws, db); + BEAST_EXPECT(once); + BEAST_EXPECT(ws.got_binary()); BEAST_EXPECT(to_string(db.data()) == "Hello"); } - ws.set_option(ping_callback{}); + ws.control_callback({}); // send ping and fragmented message - ws.set_option(ping_callback{ - [&](bool is_pong, ping_data const& payload) + once = false; + ws.control_callback( + [&](frame_type kind, string_view s) { - BEAST_EXPECT(is_pong); - BEAST_EXPECT(payload == "payload"); - }}); + BEAST_EXPECT(kind == frame_type::pong); + BEAST_EXPECT(! once); + once = true; + BEAST_EXPECT(s == "payload"); + }); ws.ping("payload"); c.write_frame(ws, false, sbuf("Hello, ")); c.write_frame(ws, false, sbuf("")); c.write_frame(ws, true, sbuf("World!")); { // receive echoed message - opcode op; - streambuf db; - c.read(ws, op, db); - BEAST_EXPECT(pong == 1); + multi_buffer db; + c.read(ws, db); + BEAST_EXPECT(once); BEAST_EXPECT(to_string(db.data()) == "Hello, World!"); } - ws.set_option(ping_callback{}); + ws.control_callback({}); // send pong c.pong(ws, ""); // send auto fragmented message - ws.set_option(auto_fragment{true}); - ws.set_option(write_buffer_size{8}); + ws.auto_fragment(true); + ws.write_buffer_size(8); c.write(ws, sbuf("Now is the time for all good men")); { // receive echoed message - opcode op; - streambuf sb; - c.read(ws, op, sb); - BEAST_EXPECT(to_string(sb.data()) == "Now is the time for all good men"); + multi_buffer b; + c.read(ws, b); + BEAST_EXPECT(to_string(b.data()) == "Now is the time for all good men"); } - ws.set_option(auto_fragment{false}); - ws.set_option(write_buffer_size{4096}); + ws.auto_fragment(false); + ws.write_buffer_size(4096); // send message with write buffer limit { std::string s(2000, '*'); - ws.set_option(write_buffer_size(1200)); + ws.write_buffer_size(1200); c.write(ws, buffer(s.data(), s.size())); { // receive echoed message - opcode op; - streambuf db; - c.read(ws, op, db); + multi_buffer db; + c.read(ws, db); BEAST_EXPECT(to_string(db.data()) == s); } } // cause ping - ws.set_option(message_type(opcode::binary)); + ws.binary(true); c.write(ws, sbuf("PING")); - ws.set_option(message_type(opcode::text)); + ws.binary(false); c.write(ws, sbuf("Hello")); { // receive echoed message - opcode op; - streambuf db; - c.read(ws, op, db); - BEAST_EXPECT(op == opcode::text); + multi_buffer db; + c.read(ws, db); + BEAST_EXPECT(ws.got_text()); BEAST_EXPECT(to_string(db.data()) == "Hello"); } // cause close - ws.set_option(message_type(opcode::binary)); + ws.binary(true); c.write(ws, sbuf("CLOSE")); restart(error::closed); // send bad utf8 - ws.set_option(message_type(opcode::binary)); + ws.binary(true); c.write(ws, buffer_cat(sbuf("TEXT"), cbuf(0x03, 0xea, 0xf0, 0x28, 0x8c, 0xbc))); restart(error::failed); // cause bad utf8 - ws.set_option(message_type(opcode::binary)); + ws.binary(true); c.write(ws, buffer_cat(sbuf("TEXT"), cbuf(0x03, 0xea, 0xf0, 0x28, 0x8c, 0xbc))); c.write(ws, sbuf("Hello")); restart(error::failed); // cause bad close - ws.set_option(message_type(opcode::binary)); + ws.binary(true); c.write(ws, buffer_cat(sbuf("RAW"), cbuf(0x88, 0x02, 0x03, 0xed))); restart(error::failed); @@ -1261,10 +1801,10 @@ public: restart(error::closed); // message size exceeds max - ws.set_option(read_message_max{1}); + ws.read_message_max(1); c.write(ws, cbuf(0x00, 0x00)); restart(error::failed); - ws.set_option(read_message_max{16*1024*1024}); + ws.read_message_max(16*1024*1024); } } catch(system_error const&) @@ -1276,25 +1816,26 @@ public: BEAST_EXPECT(n < limit); } - void run() override + void + run() override { - static_assert(std::is_constructible< - stream, boost::asio::io_service&>::value, ""); + BOOST_STATIC_ASSERT(std::is_constructible< + stream, boost::asio::io_service&>::value); - static_assert(std::is_move_constructible< - stream>::value, ""); + BOOST_STATIC_ASSERT(std::is_move_constructible< + stream>::value); - static_assert(std::is_move_assignable< - stream>::value, ""); + BOOST_STATIC_ASSERT(std::is_move_assignable< + stream>::value); - static_assert(std::is_constructible< - stream, socket_type&>::value, ""); + BOOST_STATIC_ASSERT(std::is_constructible< + stream, socket_type&>::value); - static_assert(std::is_move_constructible< - stream>::value, ""); + BOOST_STATIC_ASSERT(std::is_move_constructible< + stream>::value); - static_assert(! std::is_move_assignable< - stream>::value, ""); + BOOST_STATIC_ASSERT(! std::is_move_assignable< + stream>::value); log << "sizeof(websocket::stream) == " << sizeof(websocket::stream) << std::endl; @@ -1304,6 +1845,7 @@ public: testOptions(); testAccept(); + testHandshake(); testBadHandshakes(); testBadResponses(); @@ -1318,12 +1860,11 @@ public: server.open(any, ec); BEAST_EXPECTS(! ec, ec.message()); auto const ep = server.local_endpoint(); - testDecorator(ep); - //testInvokable1(ep); - testInvokable2(ep); - testInvokable3(ep); - testInvokable4(ep); - //testInvokable5(ep); + //testPausation1(ep); + testPausation2(ep); + testPausation3(ep); + testPausation4(ep); + //testPausation5(ep); testWriteFrames(ep); testAsyncWriteFrame(ep); } @@ -1376,6 +1917,7 @@ public: pmd.server_enable = false; doClientTests(pmd); + #if ! BEAST_NO_SLOW_TESTS pmd.client_enable = true; pmd.server_enable = true; pmd.client_max_window_bits = 10; @@ -1387,6 +1929,7 @@ public: pmd.client_max_window_bits = 10; pmd.client_no_context_takeover = true; doClientTests(pmd); + #endif } }; diff --git a/test/websocket/utf8_checker.cpp b/test/websocket/utf8_checker.cpp index 20dc3f37c2..dc01a33258 100644 --- a/test/websocket/utf8_checker.cpp +++ b/test/websocket/utf8_checker.cpp @@ -9,7 +9,7 @@ #include #include -#include +#include #include #include @@ -49,7 +49,7 @@ public: BEAST_EXPECT(! utf8.write(&(*it), 1)); // Invalid sequence - std::fill(buf.begin(), buf.end(), 0xFF); + std::fill(buf.begin(), buf.end(), '\xff'); BEAST_EXPECT(! utf8.write(&buf.front(), buf.size())); } @@ -379,15 +379,15 @@ public: consuming_buffers< boost::asio::const_buffers_1> cb{ boost::asio::const_buffers_1(s.data(), n)}; - streambuf sb{size}; + multi_buffer b; while(n) { auto const amount = (std::min)(n, size); - sb.commit(buffer_copy(sb.prepare(amount), cb)); + b.commit(buffer_copy(b.prepare(amount), cb)); cb.consume(amount); n -= amount; } - BEAST_EXPECT(utf8.write(sb.data())); + BEAST_EXPECT(utf8.write(b.data())); BEAST_EXPECT(utf8.finish()); } } @@ -403,7 +403,9 @@ public: } }; +#if defined(NDEBUG) && ! BEAST_NO_SLOW_TESTS BEAST_DEFINE_TESTSUITE(utf8_checker,websocket,beast); +#endif } // detail } // websocket diff --git a/test/websocket/websocket_async_echo_server.hpp b/test/websocket/websocket_async_echo_server.hpp index 4932e35e5f..9057e3cc56 100644 --- a/test/websocket/websocket_async_echo_server.hpp +++ b/test/websocket/websocket_async_echo_server.hpp @@ -8,8 +8,7 @@ #ifndef BEAST_WEBSOCKET_ASYNC_ECHO_SERVER_HPP #define BEAST_WEBSOCKET_ASYNC_ECHO_SERVER_HPP -#include -#include +#include #include #include #include @@ -38,25 +37,6 @@ public: using endpoint_type = boost::asio::ip::tcp::endpoint; private: - struct identity - { - template - void - operator()(beast::http::message< - true, Body, Fields>& req) const - { - req.fields.replace("User-Agent", "async_echo_client"); - } - - template - void - operator()(beast::http::message< - false, Body, Fields>& resp) const - { - resp.fields.replace("Server", "async_echo_server"); - } - }; - /** A container of type-erased option setters. */ template @@ -159,8 +139,6 @@ public: , acceptor_(ios_) , work_(ios_) { - opts_.set_option( - beast::websocket::decorate(identity{})); thread_.reserve(threads); for(std::size_t i = 0; i < threads; ++i) thread_.emplace_back( @@ -223,7 +201,7 @@ public: return fail("listen", ec); acceptor_.async_accept(sock_, ep_, std::bind(&async_echo_server::on_accept, this, - beast::asio::placeholders::error)); + std::placeholders::_1)); } private: @@ -236,8 +214,7 @@ private: int state = 0; beast::websocket::stream ws; boost::asio::io_service::strand strand; - beast::websocket::opcode op; - beast::streambuf db; + beast::multi_buffer db; std::size_t id; data(async_echo_server& server_, @@ -282,7 +259,13 @@ private: void run() { auto& d = *d_; - d.ws.async_accept(std::move(*this)); + d.ws.async_accept_ex( + [](beast::websocket::response_type& res) + { + res.insert( + "Server", "async_echo_server"); + }, + std::move(*this)); } template @@ -328,7 +311,7 @@ private: d.db.consume(d.db.size()); // read message d.state = 2; - d.ws.async_read(d.op, d.db, + d.ws.async_read(d.db, d.strand.wrap(std::move(*this))); return; @@ -348,9 +331,7 @@ private: else if(match(d.db, "TEXT")) { d.state = 1; - d.ws.set_option( - beast::websocket::message_type{ - beast::websocket::opcode::text}); + d.ws.binary(false); d.ws.async_write( d.db.data(), d.strand.wrap(std::move(*this))); return; @@ -375,8 +356,7 @@ private: } // write message d.state = 1; - d.ws.set_option( - beast::websocket::message_type(d.op)); + d.ws.binary(d.ws.got_binary()); d.ws.async_write(d.db.data(), d.strand.wrap(std::move(*this))); return; @@ -420,7 +400,7 @@ private: peer{*this, ep_, std::move(sock_)}; acceptor_.async_accept(sock_, ep_, std::bind(&async_echo_server::on_accept, this, - beast::asio::placeholders::error)); + std::placeholders::_1)); } }; diff --git a/test/websocket/websocket_sync_echo_server.hpp b/test/websocket/websocket_sync_echo_server.hpp index 8f1dbf0757..ab4384e9ad 100644 --- a/test/websocket/websocket_sync_echo_server.hpp +++ b/test/websocket/websocket_sync_echo_server.hpp @@ -8,8 +8,7 @@ #ifndef BEAST_WEBSOCKET_SYNC_ECHO_SERVER_HPP #define BEAST_WEBSOCKET_SYNC_ECHO_SERVER_HPP -#include -#include +#include #include #include #include @@ -38,25 +37,6 @@ public: using socket_type = boost::asio::ip::tcp::socket; private: - struct identity - { - template - void - operator()(beast::http::message< - true, Body, Fields>& req) const - { - req.fields.replace("User-Agent", "sync_echo_client"); - } - - template - void - operator()(beast::http::message< - false, Body, Fields>& resp) const - { - resp.fields.replace("Server", "sync_echo_server"); - } - }; - /** A container of type-erased option setters. */ template @@ -151,8 +131,6 @@ public: , sock_(ios_) , acceptor_(ios_) { - opts_.set_option( - beast::websocket::decorate(identity{})); } /** Destructor. @@ -212,7 +190,7 @@ public: return fail("listen", ec); acceptor_.async_accept(sock_, ep_, std::bind(&sync_echo_server::on_accept, this, - beast::asio::placeholders::error)); + std::placeholders::_1)); thread_ = std::thread{[&]{ ios_.run(); }}; } @@ -231,7 +209,7 @@ private: void fail(std::string what, error_code ec, - int id, endpoint_type const& ep) + std::size_t id, endpoint_type const& ep) { if(log_) if(ec != beast::websocket::error::closed) @@ -280,7 +258,7 @@ private: std::thread{lambda{*this, ep_, std::move(sock_)}}.detach(); acceptor_.async_accept(sock_, ep_, std::bind(&sync_echo_server::on_accept, this, - beast::asio::placeholders::error)); + std::placeholders::_1)); } template @@ -312,7 +290,13 @@ private: socket_type> ws{std::move(sock)}; opts_.set_options(ws); error_code ec; - ws.accept(ec); + ws.accept_ex( + [](beast::websocket::response_type& res) + { + res.insert( + "Server", "sync_echo_server"); + }, + ec); if(ec) { fail("accept", ec, id, ep); @@ -320,42 +304,39 @@ private: } for(;;) { - beast::websocket::opcode op; - beast::streambuf sb; - ws.read(op, sb, ec); + beast::multi_buffer b; + ws.read(b, ec); if(ec) { auto const s = ec.message(); break; } - ws.set_option(beast::websocket::message_type{op}); - if(match(sb, "RAW")) + ws.binary(ws.got_binary()); + if(match(b, "RAW")) { boost::asio::write( - ws.next_layer(), sb.data(), ec); + ws.next_layer(), b.data(), ec); } - else if(match(sb, "TEXT")) + else if(match(b, "TEXT")) { - ws.set_option( - beast::websocket::message_type{ - beast::websocket::opcode::text}); - ws.write(sb.data(), ec); + ws.binary(false); + ws.write(b.data(), ec); } - else if(match(sb, "PING")) + else if(match(b, "PING")) { beast::websocket::ping_data payload; - sb.consume(buffer_copy( + b.consume(buffer_copy( buffer(payload.data(), payload.size()), - sb.data())); + b.data())); ws.ping(payload, ec); } - else if(match(sb, "CLOSE")) + else if(match(b, "CLOSE")) { ws.close({}, ec); } else { - ws.write(sb.data(), ec); + ws.write(b.data(), ec); } if(ec) break; diff --git a/test/zlib/CMakeLists.txt b/test/zlib/CMakeLists.txt index 4d955e26ad..6d3c6f507e 100644 --- a/test/zlib/CMakeLists.txt +++ b/test/zlib/CMakeLists.txt @@ -2,8 +2,32 @@ GroupSources(extras/beast extras) GroupSources(include/beast beast) + GroupSources(test/zlib "/") +set(ZLIB_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/crc32.h + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/deflate.h + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/inffast.h + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/inffixed.h + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/inflate.h + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/inftrees.h + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/trees.h + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/zlib.h + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/zutil.h + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/adler32.c + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/compress.c + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/crc32.c + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/deflate.c + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/infback.c + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/inffast.c + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/inflate.c + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/inftrees.c + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/trees.c + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/uncompr.c + ${CMAKE_CURRENT_LIST_DIR}/zlib-1.2.8/zutil.c +) + if (MSVC) set_source_files_properties (${ZLIB_SOURCES} PROPERTIES COMPILE_FLAGS "/wd4127 /wd4131 /wd4244") endif() @@ -19,8 +43,8 @@ add_executable (zlib-tests inflate_stream.cpp ) -if (NOT WIN32) - target_link_libraries(zlib-tests ${Boost_LIBRARIES} Threads::Threads) -else() - target_link_libraries(zlib-tests ${Boost_LIBRARIES}) -endif() +target_link_libraries(zlib-tests + Beast + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ) diff --git a/test/zlib/Jamfile b/test/zlib/Jamfile new file mode 100644 index 0000000000..deea895cc0 --- /dev/null +++ b/test/zlib/Jamfile @@ -0,0 +1,24 @@ +# +# 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) +# + +unit-test zlib-tests : + ../../extras/beast/unit_test/main.cpp + zlib-1.2.8/adler32.c + zlib-1.2.8/compress.c + zlib-1.2.8/crc32.c + zlib-1.2.8/deflate.c + zlib-1.2.8/infback.c + zlib-1.2.8/inffast.c + zlib-1.2.8/inflate.c + zlib-1.2.8/inftrees.c + zlib-1.2.8/trees.c + zlib-1.2.8/uncompr.c + zlib-1.2.8/zutil.c + deflate_stream.cpp + error.cpp + inflate_stream.cpp + ; diff --git a/test/zlib/deflate_stream.cpp b/test/zlib/deflate_stream.cpp index a827659253..c1b2b38f58 100644 --- a/test/zlib/deflate_stream.cpp +++ b/test/zlib/deflate_stream.cpp @@ -305,6 +305,7 @@ public: { doMatrix("1.beast ", "Hello, world!", &self::doDeflate1_beast); doMatrix("1.zlib ", "Hello, world!", &self::doDeflate1_zlib); + #if ! BEAST_NO_SLOW_TESTS doMatrix("2.beast ", "Hello, world!", &self::doDeflate2_beast); doMatrix("2.zlib ", "Hello, world!", &self::doDeflate2_zlib); { @@ -317,6 +318,7 @@ public: doMatrix("4.beast ", s, &self::doDeflate1_beast); doMatrix("4.zlib ", s, &self::doDeflate1_zlib); } + #endif } void diff --git a/test/zlib/error.cpp b/test/zlib/error.cpp index 8623a38edf..eb40185dfb 100644 --- a/test/zlib/error.cpp +++ b/test/zlib/error.cpp @@ -34,24 +34,24 @@ public: void run() override { - check("zlib", error::need_buffers); - check("zlib", error::end_of_stream); - check("zlib", error::stream_error); + check("beast.zlib", error::need_buffers); + check("beast.zlib", error::end_of_stream); + check("beast.zlib", error::stream_error); - check("zlib", error::invalid_block_type); - check("zlib", error::invalid_stored_length); - check("zlib", error::too_many_symbols); - check("zlib", error::invalid_code_lenths); - check("zlib", error::invalid_bit_length_repeat); - check("zlib", error::missing_eob); - check("zlib", error::invalid_literal_length); - check("zlib", error::invalid_distance_code); - check("zlib", error::invalid_distance); + check("beast.zlib", error::invalid_block_type); + check("beast.zlib", error::invalid_stored_length); + check("beast.zlib", error::too_many_symbols); + check("beast.zlib", error::invalid_code_lenths); + check("beast.zlib", error::invalid_bit_length_repeat); + check("beast.zlib", error::missing_eob); + check("beast.zlib", error::invalid_literal_length); + check("beast.zlib", error::invalid_distance_code); + check("beast.zlib", error::invalid_distance); - check("zlib", error::over_subscribed_length); - check("zlib", error::incomplete_length_set); + check("beast.zlib", error::over_subscribed_length); + check("beast.zlib", error::incomplete_length_set); - check("zlib", error::general); + check("beast.zlib", error::general); } }; diff --git a/test/zlib/inflate_stream.cpp b/test/zlib/inflate_stream.cpp index 31f14ac582..1af12d7f88 100644 --- a/test/zlib/inflate_stream.cpp +++ b/test/zlib/inflate_stream.cpp @@ -325,6 +325,8 @@ public: m("1. beast", Beast{half, half}, check); m("1. zlib ", ZLib {half, half}, check); } + + #if ! BEAST_NO_SLOW_TESTS { Matrix m{*this}; auto const check = corpus1(50000); @@ -380,6 +382,7 @@ public: m("8. beast", Beast{full, once, Flush::block}, check); m("8. zlib ", ZLib {full, once, Z_BLOCK}, check); } + #endif // VFALCO Fails, but I'm unsure of what the correct // behavior of Z_TREES/Flush::trees is.