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..fe86fa15c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -83,4 +83,4 @@ after_script: notifications: email: - false + false \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dcb1650c7..bdb9691f81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,32 @@ +* CMake hide command lines in .vcxproj Output windows" + +WebSocket: + +* Add is_upgrade() free function + +API Changes: + +* Provide websocket::stream accept() overloads +* Refactor websocket decorators + +-------------------------------------------------------------------------------- + +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 + +-------------------------------------------------------------------------------- + 1.0.0-b34 * Fix and tidy up CMake build scripts diff --git a/CMakeLists.txt b/CMakeLists.txt index c82014589f..ed8c97cc18 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,12 +7,13 @@ project (Beast) set_property (GLOBAL PROPERTY USE_FOLDERS 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 "${CMAKE_CXX_FLAGS} /wd4100 /wd4244 /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") @@ -44,29 +45,30 @@ option (Boost_USE_STATIC_LIBS "Use static libraries for boost" ON) set (Boost_NO_SYSTEM_PATHS ON) set (Boost_USE_MULTITHREADED ON) +add_definitions (-DBOOST_COROUTINES_NO_DEPRECATION_WARNING=1) # for asio unset (Boost_INCLUDE_DIR CACHE) unset (Boost_LIBRARY_DIRS CACHE) -find_package (Boost REQUIRED COMPONENTS - coroutine - context - filesystem - program_options - system - thread - ) +if (MSVC) + find_package (Boost REQUIRED) +else() + find_package (Boost REQUIRED COMPONENTS + coroutine + context + filesystem + program_options + system + thread + ) + link_libraries (${Boost_LIBRARIES}) +endif() 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 @@ -164,15 +166,4 @@ file(GLOB_RECURSE EXTRAS_INCLUDES ${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) -endif() - -add_subdirectory (test) -add_subdirectory (test/core) -add_subdirectory (test/http) add_subdirectory (test/websocket) -add_subdirectory (test/zlib) diff --git a/Jamroot b/Jamroot index 277100ab23..aa9c5695c4 100644 --- a/Jamroot +++ b/Jamroot @@ -8,6 +8,8 @@ import os ; import feature ; import boost ; +import modules ; +import testing ; boost.use-project ; @@ -125,4 +127,3 @@ project beast ; build-project test ; -build-project examples ; diff --git a/README.md b/README.md index 1dcf4b52a1..0bc8cba4b8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,13 @@ 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) +[![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) +[![Build status](https://ci.appveyor.com/api/projects/status/g0llpbvhpjuxjnlw?svg=true)](https://ci.appveyor.com/project/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 @@ -106,24 +112,18 @@ 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: ``` cd bin cmake .. # for 32-bit Windows builds +cmake -G Xcode .. # for Apple Xcode 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: +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) -``` -cd bin -cmake -G Xcode .. # for Apple Xcode builds ``` To build with Boost.Build, it is necessary to have the bjam executable diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000000..8e25c74451 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,100 @@ +# 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 + - 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/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/examples toolset=msvc-14.0 + - b2 libs/beast/test toolset=msvc-14.0 diff --git a/doc/http.qbk b/doc/http.qbk index 9249fd64fb..d08a3046f5 100644 --- a/doc/http.qbk +++ b/doc/http.qbk @@ -26,7 +26,6 @@ contents: Algorithms Write Read - Parse Examples Send Request Receive Response @@ -136,7 +135,7 @@ object: [[HTTP Request] [HTTP Response]] [[ ``` - request req; + request req; req.version = 11; // HTTP/1.1 req.method = "GET"; req.url = "/index.htm" @@ -227,15 +226,6 @@ 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). @@ -303,7 +293,7 @@ operations performed). To send messages synchronously, use one of the ``` void send_request(boost::asio::ip::tcp::socket& sock) { - request req; + request req; req.version = 11; req.method = "GET"; req.url = "/index.html"; @@ -322,7 +312,7 @@ An asynchronous interface is available: ``` void handle_write(boost::system::error_code); ... - request req; + request req; ... async_write(sock, req, std::bind(&handle_write, std::placeholders::_1)); ``` diff --git a/doc/master.qbk b/doc/master.qbk index 24494c3886..90b697c351 100644 --- a/doc/master.qbk +++ b/doc/master.qbk @@ -107,7 +107,6 @@ provides implementations of the HTTP and WebSocket protocols. [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] diff --git a/doc/quickref.xml b/doc/quickref.xml index 874c5a6936..f623a339e2 100644 --- a/doc/quickref.xml +++ b/doc/quickref.xml @@ -31,13 +31,12 @@ basic_dynabuf_body basic_fields - basic_parser_v1 - empty_body + basic_parser fields header - header_parser_v1 + header_parser message - parser_v1 + message_parser request request_header response @@ -49,6 +48,7 @@ ext_list + opt_token_list param_list token_list @@ -57,7 +57,7 @@ Functions async_read - async_parse + async_read_some async_write chunk_encode chunk_encode_final @@ -65,17 +65,15 @@ is_keep_alive is_upgrade operator<< - parse prepare read + read_some reason_string - with_body write Type Traits is_Body - is_Parser is_Reader is_Writer has_reader @@ -83,26 +81,16 @@ - Options - - header_max_size - body_max_size - skip_body - Constants - body_what connection - no_content_length - parse_error - parse_flag + error Concepts Body Field FieldSequence - Parser Reader Writer @@ -119,12 +107,12 @@ Functions async_teardown + is_upgrade teardown Options auto_fragment - decorate keep_alive message_type permessage_deflate @@ -165,6 +153,7 @@ Classes async_completion + basic_flat_streambuf basic_streambuf buffers_adapter consuming_buffers @@ -173,6 +162,7 @@ error_category error_code error_condition + flat_streambuf handler_alloc handler_ptr static_streambuf diff --git a/doc/reference.xsl b/doc/reference.xsl index 7a4420983e..5e27971e03 100644 --- a/doc/reference.xsl +++ b/doc/reference.xsl @@ -61,6 +61,22 @@ + + + + + + + + + + + + + + + + @@ -394,13 +410,34 @@ ` + + + + + + + + - * - - - - + + + + + + + + + # + + + * + + + [*] @@ -429,6 +466,25 @@ ]] + + + [table + + ] + + + + [ + + ] + + + + [ + + ] + + diff --git a/doc/source.dox b/doc/source.dox index 624ab63310..f4707c4198 100644 --- a/doc/source.dox +++ b/doc/source.dox @@ -282,8 +282,7 @@ EXPAND_ONLY_PREDEF = YES SEARCH_INCLUDES = YES INCLUDE_PATH = ../ INCLUDE_FILE_PATTERNS = -PREDEFINED = DOXYGEN \ - GENERATING_DOCS +PREDEFINED = BEAST_DOXYGEN EXPAND_AS_DEFINED = SKIP_FUNCTION_MACROS = YES diff --git a/doc/types/DynamicBuffer.qbk b/doc/types/DynamicBuffer.qbk index 792c25914d..009abbdd3a 100644 --- a/doc/types/DynamicBuffer.qbk +++ b/doc/types/DynamicBuffer.qbk @@ -19,7 +19,9 @@ 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 + [link beast.ref.basic_flat_streambuf `basic_flat_streambuf`]. * 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 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 index 0fd852f1c9..bbf7739643 100644 --- a/doc/types/Reader.qbk +++ b/doc/types/Reader.qbk @@ -10,57 +10,198 @@ 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. +Readers come in two flavors, direct and indirect: -In this table: +Direct readers provide a buffer to callers, in which body data is placed. +This type of reader is used when the bytes corresponding to the body data +are stored without transformation. The parse algorithm performs stream or +socket reads directly into the reader provided buffer, hence the name +"direct." This model avoids an unnecessary buffer copy. An example of +a [*Body] type with a direct reader is +[link beast.ref.http__string_body `string_body`]. + +Indirect readers are passed body data in a buffer managed by the parser +algorithm. This reader is appropriate when the body data is transformed +or not otherwised stored verbatim. Some examples of when an indirect +reader is appropriate: + +* When bytes corresponding to the body are written to a file + as they are parsed. + +* The content of the message is JSON, which is parsed as it is + being read in, and stored in a structured, hierarchical format. + +In the tables below: * `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`. +* `n` is a value convertible to `std::size_t` without loss of precision. + +* `v` is a value convertible to `std::uint64_t` without loss of precision. -* `p` is a `void const*` to valid memory of at least `n` bytes. +* `s` is a value of type `boost::string_ref`. * `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 +[table Direct Reader requirements [[operation] [type] [semantics, pre/post-conditions]] [ - [`X a(m);`] + [`X::is_direct`] + [`bool`] + [ + This static constant must be set to `true` to indicate that + the reader is a direct reader. + ] +] +[ + [`X::mutable_buffers_type`] + [] + [ + This member type must be present, and meet the requirements + of [*MutableBufferSequence]. It represents the type of + the writable buffers returned by the reader, in which + bytes representing the body are stored by the implementation. + ] +] +[ + [`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`. + just before parsing bytes corresponding to the body for messages + whose semantics indicate that a body is present with non-zero + length. ] ] [ - [`a.init(ec)`] - [`void`] + [`a.init()`] + [] + [ + This function is called once before any bytes corresponding + to the body are presented to the reader, for messages whose + body is determined by the end-of-file marker on a stream, + or for messages where the chunked Transfer-Encoding is + specified. + ] +] +[ + [`a.init(v)`] + [] [ - 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`. + This function is called once before any bytes corresponding + to the body are presented to the reader, for messages where + the Content-Length is specified. The value of `v` will be + set to the number of bytes indicated by the content length. ] ] [ - [`a.write(p, n, ec)`] - [`void`] + [`a.prepare(n)`] + [`mutable_buffers_type`] [ - 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`. + The implementation calls this function to obtain a mutable + buffer sequence of up to `n` bytes in size in which to place + data corresponding to the body. The buffer returned must + be at least one byte in size, and may be smaller than `n`. + ] +] +[ + [`a.commit(n)`] + [] + [ + The implementation calls this function to indicate to the + reader that `n` bytes of data have been successfully placed + into the buffer obtained through a prior call to `prepare`. + The value of `n` will be less than or equal to the size of + the buffer returned in the previous call to `prepare`. + ] +] +[ + [`a.finish()`] + [] + [ + This function is called after all the bytes corresponding + to the body have been written to the buffers and committed. ] ] ] +[table Indirect Reader requirements +[[operation] [type] [semantics, pre/post-conditions]] +[ + [`X::is_direct`] + [`bool`] + [ + This static constant must be set to `false` to indicate that + the reader is an indirect reader. + ] +] +[ + [`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 + just before parsing bytes corresponding to the body for messages + whose semantics indicate that a body is present with non-zero + length. + ] +] +[ + [`a.init(ec)`] + [] + [ + This function is called once before any bytes corresponding + to the body are presented to the reader, for messages whose + body is determined by the end-of-file market on a stream, + or for messages where the chunked Transfer-Encoding is + specified. + If `ec` is set before returning, parsing will stop + and the error will be returned to the caller. + + ] +] +[ + [`a.init(v,ec)`] + [] + [ + This function is called once before any bytes corresponding + to the body are presented to the reader, for messages where + the Content-Length is specified. The value of `v` will be + set to the number of bytes indicated by the content length. + If `ec` is set before returning, parsing will stop + and the error will be returned to the caller. + ] +] +[ + [`a.write(s,ec)`] + [] + [ + The implementation calls this function with `s` containing + bytes corresponding to the body, after removing any present + chunked encoding transformation. + If `ec` is set before returning, parsing will stop + and the error will be returned to the caller. + ] +] +[ + [`a.finish(ec)`] + [] + [ + This function is called after all the bytes corresponding + to the body have been written to the buffers and committed. + If `ec` is set before returning, parsing will stop + and the error will be returned to the caller. + ] +] +] [note Definitions for required `Reader` member functions should be declared inline so the generated code can become part of the implementation. diff --git a/doc/websocket.qbk b/doc/websocket.qbk index 77bcc2bde0..494b27a358 100644 --- a/doc/websocket.qbk +++ b/doc/websocket.qbk @@ -50,7 +50,7 @@ The WebSocket protocol is described fully in 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 +of [link beast.ref.streams.SyncStream [*`SyncStream`]] if synchronous operations are performed, or [link beast.ref.streams.AsyncStream [*`AsyncStream`]] if asynchronous operations are performed, or both. Arguments supplied during construction are diff --git a/examples/http_crawl.cpp b/examples/http_crawl.cpp index 1a794169b0..a34f067d0d 100644 --- a/examples/http_crawl.cpp +++ b/examples/http_crawl.cpp @@ -36,7 +36,7 @@ int main(int, char const*[]) ip::tcp::socket sock(ios); connect(sock, it); auto ep = sock.remote_endpoint(); - request req; + request req; req.method = "GET"; req.url = "/"; req.version = 11; diff --git a/examples/http_example.cpp b/examples/http_example.cpp index 038ac2e24d..6c060817e9 100644 --- a/examples/http_example.cpp +++ b/examples/http_example.cpp @@ -22,7 +22,7 @@ int main() r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"})); // Send HTTP request using beast - beast::http::request req; + beast::http::request req; req.method = "GET"; req.url = "/"; req.version = 11; diff --git a/examples/ssl/http_ssl_example.cpp b/examples/ssl/http_ssl_example.cpp index 30c8194043..3b9992b4a1 100644 --- a/examples/ssl/http_ssl_example.cpp +++ b/examples/ssl/http_ssl_example.cpp @@ -34,7 +34,7 @@ int main() stream.handshake(ssl::stream_base::client); // Send HTTP request over SSL using Beast - beast::http::request req; + beast::http::request req; req.method = "GET"; req.url = "/"; req.version = 11; diff --git a/examples/websocket_async_echo_server.hpp b/examples/websocket_async_echo_server.hpp index bd0c8481b6..e6ed81ef1b 100644 --- a/examples/websocket_async_echo_server.hpp +++ b/examples/websocket_async_echo_server.hpp @@ -38,25 +38,6 @@ class async_echo_server 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 +140,6 @@ class async_echo_server , 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( @@ -282,7 +261,13 @@ class async_echo_server void run() { auto& d = *d_; - d.ws.async_accept(std::move(*this)); + d.ws.async_accept_ex( + [](beast::websocket::response_type& res) + { + res.fields.insert( + "Server", "async_echo_server"); + }, + std::move(*this)); } void operator()(error_code ec, std::size_t) diff --git a/examples/websocket_sync_echo_server.hpp b/examples/websocket_sync_echo_server.hpp index 1f41b52ce1..ab8bd5d597 100644 --- a/examples/websocket_sync_echo_server.hpp +++ b/examples/websocket_sync_echo_server.hpp @@ -38,25 +38,6 @@ class sync_echo_server 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 +132,6 @@ class sync_echo_server , sock_(ios_) , acceptor_(ios_) { - opts_.set_option( - beast::websocket::decorate(identity{})); } /** Destructor. @@ -293,7 +272,13 @@ class sync_echo_server 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.fields.insert( + "Server", "sync_echo_server"); + }, + ec); if(ec) { fail("accept", ec, id, ep); 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/string_iostream.hpp b/extras/beast/test/string_iostream.hpp new file mode 100644 index 0000000000..81417084ff --- /dev/null +++ b/extras/beast/test/string_iostream.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_STRING_IOSTREAM_HPP +#define BEAST_TEST_STRING_IOSTREAM_HPP + +#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) + throw 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, prepare_buffer(read_max_, cb_)); + if(n > 0) + cb_ = cb_ + n; + else + ec = boost::asio::error::eof; + return n; + } + + template + typename async_completion::result_type + 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 completion{handler}; + ios_.post(bind_handler( + completion.handler, ec, n)); + return completion.result.get(); + } + + template + std::size_t + write_some(ConstBufferSequence const& buffers) + { + error_code ec; + auto const n = write_some(buffers, ec); + if(ec) + throw system_error{ec}; + return n; + } + + template + std::size_t + write_some( + ConstBufferSequence const& buffers, error_code&) + { + auto const n = buffer_size(buffers); + using boost::asio::buffer_size; + using boost::asio::buffer_cast; + str.reserve(str.size() + n); + for(auto const& buffer : buffers) + str.append(buffer_cast(buffer), + buffer_size(buffer)); + return n; + } + + template + typename async_completion< + WriteHandler, void(error_code)>::result_type + 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}; + get_io_service().post( + bind_handler(completion.handler, ec, bytes_transferred)); + return completion.result.get(); + } + + friend + void + teardown(websocket::teardown_tag, + string_iostream& stream, + boost::system::error_code& ec) + { + } + + 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..c630505c76 100644 --- a/extras/beast/test/string_istream.hpp +++ b/extras/beast/test/string_istream.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -125,6 +126,26 @@ class string_istream error_code{}, boost::asio::buffer_size(buffers))); return completion.result.get(); } + + friend + void + teardown(websocket::teardown_tag, + string_istream& stream, + boost::system::error_code& ec) + { + } + + 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{})); + } }; } // test diff --git a/extras/beast/test/string_ostream.hpp b/extras/beast/test/string_ostream.hpp index a939da7aab..bc5b7896e7 100644 --- a/extras/beast/test/string_ostream.hpp +++ b/extras/beast/test/string_ostream.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -53,6 +54,7 @@ class string_ostream read_some(MutableBufferSequence const& buffers, error_code& ec) { + ec = boost::asio::error::eof; return 0; } @@ -65,7 +67,7 @@ class string_ostream async_completion completion{handler}; ios_.post(bind_handler(completion.handler, - error_code{}, 0)); + boost::asio::error::eof, 0)); return completion.result.get(); } @@ -110,6 +112,26 @@ class string_ostream bind_handler(completion.handler, ec, bytes_transferred)); return completion.result.get(); } + + friend + void + teardown(websocket::teardown_tag, + string_ostream& stream, + boost::system::error_code& ec) + { + } + + 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{})); + } }; } // test diff --git a/extras/beast/test/yield_to.hpp b/extras/beast/test/yield_to.hpp index c6b3a8679a..16dea02d57 100644 --- a/extras/beast/test/yield_to.hpp +++ b/extras/beast/test/yield_to.hpp @@ -79,7 +79,7 @@ class enable_yield_to @param args Optional arguments forwarded to the callable object. */ -#if GENERATING_DOCS +#if BEAST_DOXYGEN template void yield_to(F&& f, Args&&... args); diff --git a/include/beast/core.hpp b/include/beast/core.hpp index 9377cd99fe..b0dd3fc36c 100644 --- a/include/beast/core.hpp +++ b/include/beast/core.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include diff --git a/include/beast/core/bind_handler.hpp b/include/beast/core/bind_handler.hpp index 1b2d11daf8..18c2652069 100644 --- a/include/beast/core/bind_handler.hpp +++ b/include/beast/core/bind_handler.hpp @@ -50,7 +50,7 @@ namespace beast { arguments are forwarded into the returned object. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN implementation_defined #else detail::bound_handler< diff --git a/include/beast/core/buffer_cat.hpp b/include/beast/core/buffer_cat.hpp index a0c892a716..c2ab78ee17 100644 --- a/include/beast/core/buffer_cat.hpp +++ b/include/beast/core/buffer_cat.hpp @@ -37,7 +37,7 @@ namespace beast { also a @b MutableBufferSequence, else the returned buffer sequence will be a @b ConstBufferSequence. */ -#if GENERATING_DOCS +#if BEAST_DOXYGEN template implementation_defined buffer_cat(BufferSequence const&... buffers) diff --git a/include/beast/core/buffer_concepts.hpp b/include/beast/core/buffer_concepts.hpp index 545689eb3f..aeef682f91 100644 --- a/include/beast/core/buffer_concepts.hpp +++ b/include/beast/core/buffer_concepts.hpp @@ -17,7 +17,7 @@ namespace beast { /// Determine if `T` meets the requirements of @b `BufferSequence`. template -#if GENERATING_DOCS +#if BEAST_DOXYGEN struct is_BufferSequence : std::integral_constant #else struct is_BufferSequence : detail::is_BufferSequence::type @@ -27,7 +27,7 @@ struct is_BufferSequence : detail::is_BufferSequence::type /// Determine if `T` meets the requirements of @b `ConstBufferSequence`. template -#if GENERATING_DOCS +#if BEAST_DOXYGEN struct is_ConstBufferSequence : std::integral_constant #else struct is_ConstBufferSequence : @@ -38,7 +38,7 @@ struct is_ConstBufferSequence : /// Determine if `T` meets the requirements of @b `DynamicBuffer`. template -#if GENERATING_DOCS +#if BEAST_DOXYGEN struct is_DynamicBuffer : std::integral_constant #else struct is_DynamicBuffer : detail::is_DynamicBuffer::type @@ -48,7 +48,7 @@ struct is_DynamicBuffer : detail::is_DynamicBuffer::type /// Determine if `T` meets the requirements of @b `MutableBufferSequence`. template -#if GENERATING_DOCS +#if BEAST_DOXYGEN struct is_MutableBufferSequence : std::integral_constant #else struct is_MutableBufferSequence : diff --git a/include/beast/core/buffers_adapter.hpp b/include/beast/core/buffers_adapter.hpp index 36220d0564..df9edf70fa 100644 --- a/include/beast/core/buffers_adapter.hpp +++ b/include/beast/core/buffers_adapter.hpp @@ -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; diff --git a/include/beast/core/consuming_buffers.hpp b/include/beast/core/consuming_buffers.hpp index 4d8d42a641..2e09a418c2 100644 --- a/include/beast/core/consuming_buffers.hpp +++ b/include/beast/core/consuming_buffers.hpp @@ -59,7 +59,7 @@ class consuming_buffers `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 @@ class consuming_buffers 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; diff --git a/include/beast/core/detail/ci_char_traits.hpp b/include/beast/core/detail/ci_char_traits.hpp index 7dfd0c18ce..07fecc81a0 100644 --- a/include/beast/core/detail/ci_char_traits.hpp +++ b/include/beast/core/detail/ci_char_traits.hpp @@ -10,17 +10,15 @@ #include #include -#include -#include namespace beast { namespace detail { inline char -tolower(char c) +tolower(signed char c) { - static std::array constexpr tab = {{ + static unsigned char constexpr tab[256] = { 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, @@ -37,8 +35,9 @@ tolower(char c) 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)]); + }; + return static_cast( + tab[static_cast(c)]); } template diff --git a/include/beast/core/detail/prepare_buffers.hpp b/include/beast/core/detail/prepare_buffers.hpp index 4f9441c9b3..040e087fe5 100644 --- a/include/beast/core/detail/prepare_buffers.hpp +++ b/include/beast/core/detail/prepare_buffers.hpp @@ -61,7 +61,7 @@ class prepared_buffers boost::asio::mutable_buffer, boost::asio::const_buffer>::type; -#if GENERATING_DOCS +#if BEAST_DOXYGEN /// A bidirectional iterator type that may be used to read elements. using const_iterator = implementation_defined; diff --git a/include/beast/core/dynabuf_readstream.hpp b/include/beast/core/dynabuf_readstream.hpp index e914d434d3..e749ba0150 100644 --- a/include/beast/core/dynabuf_readstream.hpp +++ b/include/beast/core/dynabuf_readstream.hpp @@ -110,7 +110,7 @@ class dynabuf_readstream /// The type of the lowest layer. using lowest_layer_type = -#if GENERATING_DOCS +#if BEAST_DOXYGEN implementation_defined; #else typename detail::get_lowest_layer< @@ -272,7 +272,7 @@ class dynabuf_readstream 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 @@ -347,7 +347,7 @@ class dynabuf_readstream 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 diff --git a/include/beast/core/error.hpp b/include/beast/core/error.hpp index b8c78687c0..0b9b667e83 100644 --- a/include/beast/core/error.hpp +++ b/include/beast/core/error.hpp @@ -24,7 +24,7 @@ using system_error = boost::system::system_error; using error_category = boost::system::error_category; /// 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 +35,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/flat_streambuf.hpp b/include/beast/core/flat_streambuf.hpp new file mode 100644 index 0000000000..8e2cba2ccf --- /dev/null +++ b/include/beast/core/flat_streambuf.hpp @@ -0,0 +1,297 @@ +// +// 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_STREAMBUF_HPP +#define BEAST_FLAT_STREAMBUF_HPP + +#include +#include +#include +#include +#include + +namespace beast { + +/** A linear dynamic buffer. + + Objects of this type meet the requirements of + @b `DynamicBuffer` and offer an additional invariant: + Buffer sequences returned by @ref data and @ref prepare + will always be of length one. + + @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_streambuf +#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: + template + friend class basic_flat_streambuf; + + using alloc_traits = + std::allocator_traits; + + static + inline + std::size_t + dist(char const* first, char const* last) + { + return static_cast(last - first); + } + + char* p_; + 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; + + /// Copy assignment (disallowed). + basic_flat_streambuf& + operator=(basic_flat_streambuf const&) = delete; + + /// Destructor. + ~basic_flat_streambuf(); + + /** Move constructor. + + The new object will have the same input sequence + and an empty output sequence. + + @note After the move, the moved-from object will + have a capacity of zero, an empty input sequence, + and an empty output sequence. + */ + basic_flat_streambuf(basic_flat_streambuf&&); + + /** Move constructor. + + The new object will have the same input sequence + and an empty output sequence. + + @note After the move, the moved-from object will + have a capacity of zero, an empty input sequence, + and an empty output sequence. + + @param alloc The allocator to associate with the + stream buffer. + */ + basic_flat_streambuf(basic_flat_streambuf&&, + Allocator const& alloc); + + /** Copy constructor. + + The new object will have a copy of the input sequence + and an empty output sequence. + */ + basic_flat_streambuf(basic_flat_streambuf const&); + + /** Copy constructor. + + The new object will have a copy of the input sequence + and an empty output sequence. + + @param alloc The allocator to associate with the + stream buffer. + */ + basic_flat_streambuf(basic_flat_streambuf const&, + Allocator const& alloc); + + /** Copy constructor. + + The new object will have a copy of the input sequence + and an empty output sequence. + */ + template + basic_flat_streambuf( + basic_flat_streambuf const&); + + /** Copy constructor. + + The new object will have a copy of the input sequence + and an empty output sequence. + + @param alloc The allocator to associate with the + stream buffer. + */ + template + basic_flat_streambuf( + basic_flat_streambuf const&, + Allocator const& alloc); + + /** Construct a flat stream buffer. + + The buffer will have a empty input and output sequences. + + @param size The initial size of the buffer, which must be + greater than zero. + + @param limit An optional parameter specifying the maximum + of the sum of the input and output sequence sizes that can + be allocated. If unspecified, the largest value of `std::size_t` + is used. + */ + explicit + basic_flat_streambuf(std::size_t size, std::size_t limit = ( + std::numeric_limits::max())); + + /** Construct a flat stream buffer. + + The buffer will have a empty input and output sequences. + + @param alloc The allocator to associate with the + stream buffer. + + @param size The initial size of the buffer, which must be + greater than zero. + + @param limit An optional parameter specifying the maximum + of the sum of the input and output sequence sizes that can + be allocated. If unspecified, the largest value of `std::size_t` + is used. + */ + basic_flat_streambuf(Allocator const& alloc, + std::size_t size, std::size_t limit = ( + std::numeric_limits::max())); + + /// 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 + { + if(out_ < end_) + return dist(in_, end_); + return dist(p_, 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); + + /** Reserve space in the stream. + + This reallocates the buffer if necessary. + + @note All previous buffers sequences obtained from + calls to @ref data or @ref prepare are invalidated. + + @param n The number of bytes to reserve. + If `n` is greater than the maximum allowed + sum of the input and output sequences, this function will + attempt to reserve space for the allowed maximum. + */ + void + reserve(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(); + + // Helper for boost::asio::read_until + template + friend + std::size_t + read_size_helper(basic_flat_streambuf< + OtherAllocator> const&, std::size_t); +}; + +using flat_streambuf = + basic_flat_streambuf>; + +} // beast + +#include + +#endif diff --git a/include/beast/core/handler_alloc.hpp b/include/beast/core/handler_alloc.hpp index 08a395c3ec..b110ec44d4 100644 --- a/include/beast/core/handler_alloc.hpp +++ b/include/beast/core/handler_alloc.hpp @@ -35,7 +35,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 diff --git a/include/beast/core/handler_concepts.hpp b/include/beast/core/handler_concepts.hpp index e118072aeb..0f4fd9f2a9 100644 --- a/include/beast/core/handler_concepts.hpp +++ b/include/beast/core/handler_concepts.hpp @@ -16,7 +16,7 @@ namespace beast { /// Determine if `T` meets the requirements of @b `CompletionHandler`. template -#if GENERATING_DOCS +#if BEAST_DOXYGEN using is_CompletionHandler = std::integral_constant; #else using is_CompletionHandler = std::integral_constant +#include + +namespace beast { + +/* Memory is laid out thusly: + + p_ ..|.. in_ ..|.. out_ ..|.. last_ ..|.. end_ +*/ + +template +basic_flat_streambuf:: +~basic_flat_streambuf() +{ + if(p_) + alloc_traits::deallocate( + this->member(), p_, dist(p_, end_)); +} + +template +basic_flat_streambuf:: +basic_flat_streambuf(basic_flat_streambuf&& other) + : detail::empty_base_optimization< + allocator_type>(std::move(other.member())) +{ + p_ = other.p_; + in_ = other.in_; + out_ = other.out_; + last_ = out_; + end_ = other.end_; + max_ = other.max_; + other.p_ = nullptr; + other.in_ = nullptr; + other.out_ = nullptr; + other.last_ = nullptr; + other.end_ = nullptr; +} + +template +basic_flat_streambuf:: +basic_flat_streambuf(basic_flat_streambuf&& other, + Allocator const& alloc) + : detail::empty_base_optimization< + allocator_type>(alloc) +{ + if(this->member() != other.member()) + { + auto const n = other.size(); + p_ = alloc_traits::allocate( + this->member(), n); + in_ = p_; + out_ = p_ + n; + last_ = out_; + end_ = out_; + max_ = other.max_; + std::memcpy(in_, other.in_, n); + return; + } + p_ = other.p_; + in_ = other.in_; + out_ = other.out_; + last_ = out_; + end_ = other.end_; + max_ = other.max_; + other.p_ = nullptr; + other.in_ = nullptr; + other.out_ = nullptr; + other.last_ = nullptr; + other.end_ = nullptr; +} + +template +basic_flat_streambuf:: +basic_flat_streambuf( + basic_flat_streambuf const& other) + : detail::empty_base_optimization( + alloc_traits::select_on_container_copy_construction( + other.member())) +{ + auto const n = other.size(); + p_ = alloc_traits::allocate( + this->member(), n); + in_ = p_; + out_ = p_ + n; + last_ = out_; + end_ = out_; + max_ = other.max_; + std::memcpy(in_, other.in_, n); +} + +template +basic_flat_streambuf:: +basic_flat_streambuf( + basic_flat_streambuf const& other, + Allocator const& alloc) + : detail::empty_base_optimization< + allocator_type>(alloc) +{ + auto const n = other.size(); + p_ = alloc_traits::allocate( + this->member(), n); + in_ = p_; + out_ = p_ + n; + last_ = out_; + end_ = out_; + max_ = other.max_; + std::memcpy(in_, other.in_, n); +} + +template +template +basic_flat_streambuf:: +basic_flat_streambuf( + basic_flat_streambuf const& other) +{ + auto const n = other.size(); + p_ = alloc_traits::allocate( + this->member(), n); + in_ = p_; + out_ = p_ + n; + last_ = out_; + end_ = out_; + max_ = other.max_; + std::memcpy(in_, other.in_, n); +} + +template +template +basic_flat_streambuf:: +basic_flat_streambuf( + basic_flat_streambuf const& other, + Allocator const& alloc) + : detail::empty_base_optimization< + allocator_type>(alloc) +{ + auto const n = other.size(); + p_ = alloc_traits::allocate( + this->member(), n); + in_ = p_; + out_ = p_ + n; + last_ = out_; + end_ = out_; + max_ = other.max_; + std::memcpy(in_, other.in_, n); +} + +template +basic_flat_streambuf:: +basic_flat_streambuf( + std::size_t size, std::size_t limit) + : p_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(limit) +{ + BOOST_ASSERT(size > 0); + reserve(size); +} + +template +basic_flat_streambuf:: +basic_flat_streambuf(Allocator const& alloc, + std::size_t size, std::size_t limit) + : detail::empty_base_optimization< + allocator_type>(alloc) + , p_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(limit) +{ + BOOST_ASSERT(size > 0); + reserve(size); +} + +template +auto +basic_flat_streambuf:: +prepare(std::size_t n) -> + mutable_buffers_type +{ + if(n <= dist(out_, end_)) + { + last_ = out_ + n; + return{out_, n}; + } + auto const len = size(); + if(n <= dist(p_, end_) - len) + { + std::memmove(p_, in_, len); + in_ = p_; + out_ = in_ + len; + last_ = out_ + n; + return {out_, n}; + } + if(n > max_ - len) + throw std::length_error{ + "flat_streambuf overflow"}; + auto const p = alloc_traits::allocate( + this->member(), len + n); + if(len > 0) + std::memcpy(p, in_, len); + alloc_traits::deallocate( + this->member(), p_, dist(p_, end_)); + p_ = p; + in_ = p_; + out_ = in_ + len; + last_ = out_ + n; + end_ = last_; + return {out_, n}; +} + +template +void +basic_flat_streambuf:: +consume(std::size_t n) +{ + if(n >= dist(in_, out_)) + { + in_ = p_; + out_ = p_; + return; + } + in_ += n; +} + +template +void +basic_flat_streambuf:: +reserve(std::size_t n) +{ + if(n <= dist(p_, end_)) + return; + if(n > max_) + throw std::length_error{ + "flat_streambuf overflow"}; + auto const p = alloc_traits::allocate( + this->member(), n); + auto const len = size(); + if(len > 0) + std::memcpy(p, in_, len); + alloc_traits::deallocate( + this->member(), p_, dist(p_, end_)); + p_ = p; + in_ = p_; + out_ = p_ + len; + last_ = out_; + end_ = p + n; +} + +template +void +basic_flat_streambuf:: +shrink_to_fit() +{ + auto const len = size(); + if(len == dist(p_, end_)) + return; + char* p; + if(len > 0) + { + p = alloc_traits::allocate( + this->member(), len); + std::memcpy(p, in_, len); + } + else + { + p = nullptr; + } + alloc_traits::deallocate( + this->member(), p_, dist(p_, end_)); + p_ = p; + in_ = p_; + out_ = p_ + len; + last_ = out_; + end_ = out_; +} + +template +std::size_t +read_size_helper(basic_flat_streambuf< + Allocator> const& fb, std::size_t max_size) +{ + // If this goes off it means you forgot to + // call reserve() before using the buffer. + BOOST_ASSERT(fb.capacity() != 0); + BOOST_ASSERT(max_size >= 1); + auto const len = fb.size(); + auto const avail = fb.capacity() - len; + if (avail > 0) + return (std::min)(avail, max_size); + auto size = (std::min)( + fb.capacity() * 2, fb.max_size()) - len; + if(size == 0) + size = 1; + return (std::min)(size, max_size); +} + +} // beast + +#endif diff --git a/include/beast/core/prepare_buffers.hpp b/include/beast/core/prepare_buffers.hpp index 7db31b5b80..0a960ea8e4 100644 --- a/include/beast/core/prepare_buffers.hpp +++ b/include/beast/core/prepare_buffers.hpp @@ -37,7 +37,7 @@ namespace beast { memory is not transferred. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN implementation_defined #else inline diff --git a/include/beast/core/static_streambuf.hpp b/include/beast/core/static_streambuf.hpp index 75ced50a37..2a0d49e159 100644 --- a/include/beast/core/static_streambuf.hpp +++ b/include/beast/core/static_streambuf.hpp @@ -28,7 +28,7 @@ namespace beast { */ class static_streambuf { -#if GENERATING_DOCS +#if BEAST_DOXYGEN private: #else protected: @@ -40,7 +40,7 @@ class static_streambuf std::uint8_t* end_; 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; @@ -116,7 +116,7 @@ class static_streambuf in_ += std::min(n, out_ - in_); } -#if GENERATING_DOCS +#if BEAST_DOXYGEN private: #else protected: @@ -150,7 +150,7 @@ class static_streambuf template class static_streambuf_n : public static_streambuf -#if ! GENERATING_DOCS +#if ! BEAST_DOXYGEN , private boost::base_from_member< std::array> #endif @@ -158,14 +158,14 @@ class static_streambuf_n using member_type = boost::base_from_member< std::array>; public: -#if GENERATING_DOCS +#if BEAST_DOXYGEN private: #endif static_streambuf_n( static_streambuf_n const&) = delete; static_streambuf_n& operator=( static_streambuf_n const&) = delete; -#if GENERATING_DOCS +#if BEAST_DOXYGEN public: #endif diff --git a/include/beast/core/stream_concepts.hpp b/include/beast/core/stream_concepts.hpp index 5f0ba3adfa..f3609e4b97 100644 --- a/include/beast/core/stream_concepts.hpp +++ b/include/beast/core/stream_concepts.hpp @@ -16,7 +16,7 @@ namespace beast { /// Determine if `T` has the `get_io_service` member. template -#if GENERATING_DOCS +#if BEAST_DOXYGEN struct has_get_io_service : std::integral_constant{}; #else using has_get_io_service = typename detail::has_get_io_service::type; @@ -24,7 +24,7 @@ using has_get_io_service = typename detail::has_get_io_service::type; /// Determine if `T` meets the requirements of @b `AsyncReadStream`. template -#if GENERATING_DOCS +#if BEAST_DOXYGEN struct is_AsyncReadStream : std::integral_constant{}; #else using is_AsyncReadStream = typename detail::is_AsyncReadStream::type; @@ -32,7 +32,7 @@ using is_AsyncReadStream = typename detail::is_AsyncReadStream::type; /// Determine if `T` meets the requirements of @b `AsyncWriteStream`. template -#if GENERATING_DOCS +#if BEAST_DOXYGEN struct is_AsyncWriteStream : std::integral_constant{}; #else using is_AsyncWriteStream = typename detail::is_AsyncWriteStream::type; @@ -40,7 +40,7 @@ using is_AsyncWriteStream = typename detail::is_AsyncWriteStream::type; /// Determine if `T` meets the requirements of @b `SyncReadStream`. template -#if GENERATING_DOCS +#if BEAST_DOXYGEN struct is_SyncReadStream : std::integral_constant{}; #else using is_SyncReadStream = typename detail::is_SyncReadStream::type; @@ -48,7 +48,7 @@ using is_SyncReadStream = typename detail::is_SyncReadStream::type; /// Determine if `T` meets the requirements of @b `SyncWriterStream`. template -#if GENERATING_DOCS +#if BEAST_DOXYGEN struct is_SyncWriteStream : std::integral_constant{}; #else using is_SyncWriteStream = typename detail::is_SyncWriteStream::type; @@ -56,7 +56,7 @@ using is_SyncWriteStream = typename detail::is_SyncWriteStream::type; /// Determine if `T` meets the requirements of @b `AsyncStream`. template -#if GENERATING_DOCS +#if BEAST_DOXYGEN struct is_AsyncStream : std::integral_constant{}; #else using is_AsyncStream = std::integral_constant -#if GENERATING_DOCS +#if BEAST_DOXYGEN struct is_SyncStream : std::integral_constant{}; #else using is_SyncStream = std::integral_constant class basic_streambuf -#if ! GENERATING_DOCS +#if ! BEAST_DOXYGEN : private detail::empty_base_optimization< typename std::allocator_traits:: template rebind_alloc> #endif { public: -#if GENERATING_DOCS +#if BEAST_DOXYGEN /// The type of allocator used. using allocator_type = Allocator; #else @@ -81,7 +81,7 @@ class basic_streambuf size_type out_end_ = 0; // output end offset in list_.back() 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; diff --git a/include/beast/core/to_string.hpp b/include/beast/core/to_string.hpp index e391ea2d57..14d32589aa 100644 --- a/include/beast/core/to_string.hpp +++ b/include/beast/core/to_string.hpp @@ -29,7 +29,7 @@ namespace beast { the buffers parameter meets the requirements of @b `ConstBufferSequence`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN std::string #else typename std::enable_if< diff --git a/include/beast/core/write_dynabuf.hpp b/include/beast/core/write_dynabuf.hpp index 646e787498..c91b984c69 100644 --- a/include/beast/core/write_dynabuf.hpp +++ b/include/beast/core/write_dynabuf.hpp @@ -49,7 +49,7 @@ namespace beast { the `dynabuf` parameter meets the requirements of @b `DynamicBuffer`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void #else typename std::enable_if::value>::type diff --git a/include/beast/http.hpp b/include/beast/http.hpp index b4c7734f17..8a8c3ff34f 100644 --- a/include/beast/http.hpp +++ b/include/beast/http.hpp @@ -11,14 +11,14 @@ #include #include -#include +#include #include -#include +#include #include +#include #include -#include -#include -#include +#include +#include #include #include #include diff --git a/include/beast/http/basic_dynabuf_body.hpp b/include/beast/http/basic_dynabuf_body.hpp index a2c090149e..8ad5f69186 100644 --- a/include/beast/http/basic_dynabuf_body.hpp +++ b/include/beast/http/basic_dynabuf_body.hpp @@ -27,36 +27,53 @@ struct basic_dynabuf_body /// The type of the `message::body` member using value_type = DynamicBuffer; -#if GENERATING_DOCS +#if BEAST_DOXYGEN private: #endif class reader { - value_type& sb_; + value_type& body_; public: + static bool constexpr is_direct = true; + + using mutable_buffers_type = + typename DynamicBuffer::mutable_buffers_type; + template explicit reader(message& m) noexcept - : sb_(m.body) + basic_dynabuf_body, Fields>& msg) + : body_(msg.body) + { + } + + void + init() + { + } + + void + init(std::uint64_t content_length) + { + } + + mutable_buffers_type + prepare(std::size_t n) { + return body_.prepare(n); } void - init(error_code&) noexcept + commit(std::size_t n) { + body_.commit(n); } void - write(void const* data, - std::size_t size, error_code&) noexcept + finish() { - using boost::asio::buffer; - using boost::asio::buffer_copy; - sb_.commit(buffer_copy( - sb_.prepare(size), buffer(data, size))); } }; diff --git a/include/beast/http/basic_fields.hpp b/include/beast/http/basic_fields.hpp index 2589c827a9..f4f73ba9f1 100644 --- a/include/beast/http/basic_fields.hpp +++ b/include/beast/http/basic_fields.hpp @@ -39,7 +39,7 @@ namespace http { */ template class basic_fields : -#if ! GENERATING_DOCS +#if ! BEAST_DOXYGEN private beast::detail::empty_base_optimization< typename std::allocator_traits:: template rebind_alloc< @@ -89,17 +89,17 @@ class basic_fields : Meets the requirements of @b Field. */ -#if GENERATING_DOCS +#if BEAST_DOXYGEN using value_type = implementation_defined; #endif /// A const iterator to the field sequence -#if GENERATING_DOCS +#if BEAST_DOXYGEN using iterator = implementation_defined; #endif /// A const iterator to the field sequence -#if GENERATING_DOCS +#if BEAST_DOXYGEN using const_iterator = implementation_defined; #endif diff --git a/include/beast/http/basic_parser.hpp b/include/beast/http/basic_parser.hpp new file mode 100644 index 0000000000..755cccc179 --- /dev/null +++ b/include/beast/http/basic_parser.hpp @@ -0,0 +1,643 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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 + +namespace beast { +namespace http { + +/** Describes the parser's current state. + + The state is expressed as the type of data that + @ref basic_parser is expecting to see in subsequently + provided octets. +*/ +enum class parse_state +{ + /// Expecting one or more header octets + header = 0, + + /// Expecting one or more body octets + body = 1, + + /// Expecting zero or more body octets followed by EOF + body_to_eof = 2, + + /// Expecting additional chunk header octets + chunk_header = 3, + + /// Expecting one or more chunk body octets + chunk_body = 4, + + /** The parsing is complete. + + The parse is considered complete when the full header + is received and either the full body is received, or + the semantics of the message indicate that no body + is expected. This includes the case where the caller + has indicated to the parser that no body is expected, + for example when receiving a response to a HEAD request. + */ + complete = 5 +}; + +/** 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 interface uses CRTP (Curiously Recurring Template Pattern). + To use this class, 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 (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. + + @par Derived Example + + @code + template + struct derived + : basic_parser> + { + // The type used when providing a mutable + // buffer sequence in which to store body data. + // + using mutable_buffers_type = ...; + + // When isRequest == true, called + // after the Request Line is received. + // + void + on_request( + boost::string_ref const& method, + boost::string_ref const& path, + int version, + error_code& ec); + + // When isRequest == false, called + // after the Status Line is received. + // + void + on_response( + int status, + boost::string_ref const& reason, + int version, + error_code& ec); + + // Called after receiving a field/value pair. + // + void + on_field( + boost::string_ref const& name, + boost::string_ref const& value, + error_code& ec); + + // Called after the header is complete. + // + void + on_header( + error_code& ec); + + // Called once before the body, if any, is started. + // This will only be called if the semantics of the + // message indicate that a body exists, including + // an indicated body of zero length. + // + void + on_body(); + + // Called zero or more times to provide body data. + // + // Only used if isDirect == false + // + void + on_data( + boost::string_ref const& s, + error_code& ec); + + // Called zero or more times to retrieve a mutable + // buffer sequence in which to store body data. + // + // Only used if isDirect == true + // + mutable_buffers_type + on_prepare( + std::size_t n); + + // Called after body data has been stored in the + // buffer returned by the previous call to on_prepare. + // + // Only used if isDirect == true + // + void + on_commit( + std::size_t n); + + // If the Transfer-Encoding is specified, and the + // last item in the list of encodings is "chunked", + // called after receiving a chunk header or a final + // chunk. + // + void + on_chunk( + std::uint64_t length, // Length of this chunk + boost::string_ref const& ext, // The chunk extensions, if any + error_code& ec); + + // Called once when the message is complete. + // This will be called even if there is no body. + // + void + on_complete(error_code& ec); + }; + @endcode + + If a callback sets the error code, the error will be propagated + to the caller of the parser. Behavior of parsing after an error + is returned is undefined. + + When the parser state is positioned to read bytes belonging to + the body, calling @ref write or @ref write will implicitly + cause a buffer copy (because bytes are first transferred to the + dynamic buffer). To avoid this copy, the additional functions + @ref copy_body, @ref prepare_body, and @ref commit_body are + provided to allow the caller to read bytes directly into buffers + supplied by the parser. + + The parser is optimized for the case where the input buffer + sequence consists of a single contiguous buffer. The + @ref beast::flat_streambuf 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_streambuf 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. + + @tparam isRequest A `bool` indicating whether the parser will be + presented with request or response message. + + @tparam isDirect A `bool` indicating whether the parser interface + supports reading body data directly into parser-provided buffers. + + @tparam Derived The derived class type. This is part of the + Curiously Recurring Template Pattern interface. +*/ +template +class basic_parser + : private detail::basic_parser_base +{ + template + friend class basic_parser; + + // Message will be complete after reading header + static unsigned constexpr flagSkipBody = 1<< 0; + + + + static unsigned constexpr flagOnBody = 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 flagFinalChunk = 1<< 7; + static unsigned constexpr flagConnectionClose = 1<< 8; + static unsigned constexpr flagConnectionUpgrade = 1<< 9; + static unsigned constexpr flagConnectionKeepAlive = 1<< 10; + static unsigned constexpr flagContentLength = 1<< 11; + static unsigned constexpr flagChunked = 1<< 12; + static unsigned constexpr flagUpgrade = 1<< 13; + + std::uint64_t len_; // size of chunk or body + std::unique_ptr buf_; + std::size_t buf_len_ = 0; + std::size_t skip_ = 0; // search from here + std::size_t x_; // scratch variable + unsigned f_ = 0; // flags + parse_state state_ = parse_state::header; + boost::string_ref ext_; + boost::string_ref body_; + +public: + /// Copy constructor (disallowed) + basic_parser(basic_parser const&) = delete; + + /// Copy assignment (disallowed) + basic_parser& operator=(basic_parser const&) = delete; + + /// Default constructor + basic_parser() = default; + + /// `true` if this parser parses requests, `false` for responses. + static bool constexpr is_request = isRequest; + + /// Destructor + ~basic_parser() = default; + + /** Move constructor + + After the move, the only valid operation on the + moved-from object is destruction. + */ + template + basic_parser(basic_parser< + isRequest, OtherIsDirect, OtherDerived>&&); + + /** Set the skip body option. + + The 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. + + 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 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. + + @note This function must called before any bytes are processed. + */ + void + skip_body(); + + /** Returns the current parser state. + + The parser state indicates what octets the parser + expects to see next in the input stream. + */ + parse_state + state() const + { + return state_; + } + + /// Returns `true` if the parser has received at least one byte of input. + bool + got_some() const + { + return (f_ & flagGotSome) != 0; + } + + /// Returns `true` if the complete header has been parsed. + bool + got_header() const + { + return state_ != parse_state::header; + } + + /** Returns `true` if a Content-Length is specified. + + @note Only valid after parsing a complete header. + */ + bool + got_content_length() const + { + return (f_ & flagContentLength) != 0; + } + + /** Returns `true` if the message is complete. + + The message is complete after a full header is + parsed and one of the following is true: + + @li @ref skip_body was called + + @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 received. + */ + bool + is_complete() const + { + return state_ == parse_state::complete; + } + + /** Returns `true` if the message is an upgrade message. + + @note Only valid after parsing a complete header. + */ + bool + is_upgrade() const + { + return (f_ & flagConnectionUpgrade) != 0; + } + + /** Returns `true` if keep-alive is specified + + @note Only valid after parsing a complete header. + */ + bool + is_keep_alive() const; + + /** Returns `true` if the chunked Transfer-Encoding is specified. + + @note Only valid after parsing a complete header. + */ + bool + is_chunked() const + { + return (f_ & flagChunked) != 0; + } + + /** Write part of a buffer sequence to the parser. + + This function attempts to parse the HTTP message + stored in the caller provided buffers. Upon success, + a positive return value indicates that the parser + made forward progress, consuming that number of + bytes. + + A return value of zero indicates that the parser + requires additional input. In this case the caller + should append additional bytes to the input buffer + sequence and call @ref write again. + + @param buffers An object meeting the requirements of + @b ConstBufferSequence that represents the message. + + @param ec Set to the error, if any occurred. + + @return The number of bytes consumed in the buffer + sequence. + */ + template + std::size_t + write(ConstBufferSequence const& buffers, error_code& ec); + +#if ! GENERATING_DOCS + std::size_t + write(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 + write_eof(error_code& ec); + + /** Returns the number of bytes remaining in the body or chunk. + + If a Content-Length is specified and the parser state + is equal to @ref beast::http::parse_state::body, this will return + the number of bytes remaining in the body. If the + chunked Transfer-Encoding is indicated and the parser + state is equal to @ref beast::http::parse_state::chunk_body, this + will return the number of bytes remaining in the chunk. + Otherwise, the function behavior is undefined. + */ + std::uint64_t + size() const + { + BOOST_ASSERT( + state_ == parse_state::body || + state_ == parse_state::chunk_body); + return len_; + } + + /** Returns the body data parsed in the last call to @ref write. + + This buffer is invalidated after any call to @ref write + or @ref write_eof. + + @note If the last call to @ref write came from the input + area of a @b DynamicBuffer object, a call to the dynamic + buffer's `consume` function may invalidate this return + value. + */ + boost::string_ref const& + body() const + { + // This function not available when isDirect==true + static_assert(! isDirect, ""); + return body_; + } + + /** Returns the chunk extension parsed in the last call to @ref write. + + This buffer is invalidated after any call to @ref write + or @ref write_eof. + + @note If the last call to @ref write came from the input + area of a @b DynamicBuffer object, a call to the dynamic + buffer's `consume` function may invalidate this return + value. + */ + boost::string_ref const& + chunk_extension() const + { + // This function not available when isDirect==true + static_assert(! isDirect, ""); + return ext_; + } + + /** Returns the optional value of Content-Length if known. + + @note The return value is undefined unless a complete + header has been parsed. + */ + boost::optional + content_length() const + { + BOOST_ASSERT(got_header()); + if(! (f_ & flagContentLength)) + return boost::none; + return len_; + } + + /** Copy leftover body data from the dynamic buffer. + + @note This member function is only available when + `isDirect==true`. + + @return The number of bytes processed from the dynamic + buffer. The caller should remove these bytes by calling + `consume` on the buffer. + */ + template + std::size_t + copy_body(DynamicBuffer& dynabuf); + + /** Returns a set of buffers for storing body data. + + @note This member function is only available when + `isDirect==true`. + + @param limit The maximum number of bytes in the + size of the returned buffer sequence. The actual size + of the buffer sequence may be lower than this number. + */ + template + void + prepare_body(boost::optional< + MutableBufferSequence>& buffers, std::size_t limit); + + /** Commit body data. + + @note This member function is only available when + `isDirect==true`. + */ + void + commit_body(std::size_t n); + + /** Indicate that body octets have been consumed. + */ + void + consume(std::size_t n) + { + BOOST_ASSERT(n <= len_); + BOOST_ASSERT( + state_ == parse_state::body || + state_ == parse_state::chunk_body); + len_ -= n; + if(len_ == 0) + { + if(state_ == parse_state::body) + state_ = parse_state::complete; + else + state_ = parse_state::chunk_header; + } + } + + /** Consume all remaining body data. + + This function instructs the parser to advance the + state past any expected body octets. Callers who + wish to read and process the body themselves will + call this function. + */ + void + consume_body(error_code& ec); + +private: + inline + Derived& + impl() + { + return *static_cast(this); + } + + template + boost::string_ref + maybe_flatten( + ConstBufferSequence const& buffers); + + std::size_t + do_write(boost::asio::const_buffers_1 const& buffer, + error_code& ec, std::true_type); + + std::size_t + do_write(boost::asio::const_buffers_1 const& buffer, + error_code& ec, std::false_type); + + void + parse_startline(char const*& it, + int& version, int& status, + error_code& ec, std::true_type); + + void + parse_startline(char const*& it, + int& version, int& status, + error_code& ec, std::false_type); + + void + parse_fields(char const*& it, + char const* last, error_code& ec); + + void + do_field( + boost::string_ref const& name, + boost::string_ref const& value, + error_code& ec); + + std::size_t + parse_header(char const* p, + std::size_t n, error_code& ec); + + void + do_header(int, std::true_type); + + void + do_header(int status, std::false_type); + + void + maybe_do_body_direct(); + + void + maybe_do_body_indirect(error_code& ec); + + std::size_t + parse_chunk_header(char const* p, + std::size_t n, error_code& ec); + + std::size_t + parse_body(char const* p, + std::size_t n, error_code& ec); + + std::size_t + parse_body_to_eof(char const* p, + std::size_t n, error_code& ec); + + std::size_t + parse_chunk_body(char const* p, + std::size_t n, error_code& ec); + + void + do_complete(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/chunk_encode.hpp b/include/beast/http/chunk_encode.hpp index a437fe1839..082d2df109 100644 --- a/include/beast/http/chunk_encode.hpp +++ b/include/beast/http/chunk_encode.hpp @@ -36,7 +36,7 @@ namespace http { @see rfc7230 section 4.1.3 */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN implementation_defined #else beast::detail::buffer_cat_helper< @@ -59,7 +59,7 @@ chunk_encode(bool fin, ConstBufferSequence const& buffers) @see rfc7230 section 4.1.3 */ inline -#if GENERATING_DOCS +#if BEAST_DOXYGEN implementation_defined #else boost::asio::const_buffers_1 diff --git a/include/beast/http/concepts.hpp b/include/beast/http/concepts.hpp index 2e83fe7a48..7c58502a6e 100644 --- a/include/beast/http/concepts.hpp +++ b/include/beast/http/concepts.hpp @@ -5,13 +5,15 @@ // 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 +#ifndef BEAST_HTTP_CONCEPTS_HPP +#define BEAST_HTTP_CONCEPTS_HPP #include #include +#include #include #include +#include #include #include @@ -86,50 +88,11 @@ class is_Writer >; }; -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 +#if BEAST_DOXYGEN struct is_Body : std::integral_constant{}; #else using is_Body = detail::has_value_type; @@ -140,7 +103,7 @@ using is_Body = detail::has_value_type; @tparam T The type to check, which must meet the requirements of @b Body. */ -#if GENERATING_DOCS +#if BEAST_DOXYGEN template struct has_reader : std::integral_constant{}; #else @@ -158,7 +121,7 @@ struct has_reader struct has_writer : std::integral_constant{}; #else @@ -178,24 +141,32 @@ struct has_writer struct is_Reader : std::integral_constant {}; #else template> -struct is_Reader : std::false_type {}; +struct is_Reader : std::true_type {}; template struct is_Reader(), std::declval().init( - std::declval()), - std::declval().write( - std::declval(), - std::declval(), - std::declval()) + std::declval>()), + std::declval().prepare( + std::declval()), + std::declval().commit( + std::declval()), + std::declval().finish() )> > : std::integral_constant::value - > + is_MutableBufferSequence< + typename T::mutable_buffers_type>::value && + std::is_convertible().prepare( + std::declval())), + typename T::mutable_buffers_type + >::value> + { static_assert(std::is_same< typename M::body_type::reader, T>::value, @@ -211,20 +182,12 @@ struct is_Reader -#if GENERATING_DOCS +#if BEAST_DOXYGEN 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 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..912244545d --- /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 +{ + boost::string_ref 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 GENERATING_DOCS + 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(boost::string_ref const& 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..96e2c4f409 --- /dev/null +++ b/include/beast/http/detail/basic_parser.hpp @@ -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) +// + +#ifndef BEAST_HTTP_DETAIL_BASIC_PARSER_HPP +#define BEAST_HTTP_DETAIL_BASIC_PARSER_HPP + +#include +#include +#include +#include +#include +#include +#include + +/* + Portions of this file based on code from picophttpparser, + copyright notice below. + https://github.com/h2o/picohttpparser +*/ +/* + * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, + * Shigeo Mitsunari + * + * The software is licensed under either the MIT License (below) or the Perl + * license. + * + * 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. + */ + +namespace beast { +namespace http { +namespace detail { + +#if __GNUC__ >= 3 +# define BEAST_LIKELY(x) __builtin_expect(!!(x), 1) +# define BEAST_UNLIKELY(x) __builtin_expect(!!(x), 0) +#else +#define BEAST_LIKELY(x) (x) +#define BEAST_UNLIKELY(x) (x) +#endif + +class basic_parser_base +{ +protected: + 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 + bool + is_value_char(char c) + { + // any OCTET except CTLs and LWS + 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 + is_text(char c) + { + // VCHAR / SP / HT / obs-text + static bool constexpr tab[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 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, // 16 + 1, 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-33) < 94; + } + + static + boost::string_ref + make_string(char const* first, char const* last) + { + return {first, static_cast< + std::size_t>(last - first)}; + } + + template + static + bool + strieq(boost::string_ref const& s1, + boost::string_ref const& s2) + { + if(s1.size() != s2.size()) + return false; + auto p1 = s1.data(); + auto p2 = s2.data(); + for(auto n = s1.size(); n--; ++p1, ++p2) + if(*p1 != tolower(*p2)) + return false; + return true; + } + + template + bool + strieq(const char (&s1)[N], + boost::string_ref const& s2) + { + return strieq({s1, N-1}, s2); + } + + 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 != '\r') + return false; + if(*++it != '\n') + return false; + ++it; + return true; + } + + static + boost::string_ref + parse_method(char const*& it) + { + auto const first = it; + while(detail::is_tchar(*it)) + ++it; + return {first, static_cast< + boost::string_ref::size_type>( + it - first)}; + } + + static + boost::string_ref + parse_path(char const*& it) + { + auto const first = it; + while(is_pathchar(*it)) + ++it; + if(*it != ' ') + return {}; + return {first, static_cast< + boost::string_ref::size_type>( + it - first)}; + } + + static + boost::string_ref + parse_name(char const*& it) + { + auto const first = it; + while(to_field_char(*it)) + ++it; + return {first, static_cast< + boost::string_ref::size_type>( + it - first)}; + } + + static + int + parse_version(char const*& it) + { + if(*it != 'H') + return -1; + if(*++it != 'T') + return -1; + if(*++it != 'T') + return -1; + if(*++it != 'P') + return -1; + if(*++it != '/') + return -1; + if(! is_digit(*++it)) + return -1; + int v = 10 * (*it - '0'); + if(*++it != '.') + return -1; + if(! is_digit(*++it)) + return -1; + v += *it++ - '0'; + return v; + } + + static + int + parse_status(char const*& it) + { + int v; + if(! is_digit(*it)) + return -1; + v = 100 * (*it - '0'); + if(! is_digit(*++it)) + return -1; + v += 10 * (*it - '0'); + if(! is_digit(*++it)) + return -1; + v += (*it++ - '0'); + return v; + } + + static + boost::string_ref + parse_reason(char const*& it) + { + auto const first = it; + while(*it != '\r') + { + if(! is_text(*it)) + return {}; + ++it; + } + return {first, static_cast< + std::size_t>(it - first)}; + } + + // VFALCO Can SIMD help this? + static + char const* + find_eol( + char const* first, char const* last, + error_code& ec) + { + auto it = first; + for(;;) + { + if(it == last) + return nullptr; + if(*it == '\r') + { + if(++it == last) + return nullptr; + if(*it != '\n') + { + ec = error::bad_line_ending; + return nullptr; + } + 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* first, char const* last, + error_code& ec) + { + auto it = first; + for(;;) + { + if(it == last) + return nullptr; + if(*it == '\r') + { + if(++it == last) + return nullptr; + if(*it != '\n') + { + ec = error::bad_line_ending; + return nullptr; + } + if(++it == last) + return nullptr; + if(*it != '\r') + { + ++it; + continue; + } + if(++it == last) + return nullptr; + if(*it != '\n') + { + ec = error::bad_line_ending; + return nullptr; + } + return ++it; + } + // VFALCO Should we handle the legacy case + // for lines terminated with a single '\n'? + ++it; + } + } +}; + +} // 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/rfc7230.hpp b/include/beast/http/detail/rfc7230.hpp index a68b4da835..518efb8284 100644 --- a/include/beast/http/detail/rfc7230.hpp +++ b/include/beast/http/detail/rfc7230.hpp @@ -46,7 +46,7 @@ is_alpha(char c) 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)]; + return tab[static_cast(c)]; } inline @@ -73,7 +73,7 @@ is_text(char c) 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)]; + return tab[static_cast(c)]; } inline @@ -105,7 +105,7 @@ is_tchar(char c) 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)]; + return tab[static_cast(c)]; } inline @@ -134,7 +134,7 @@ is_qdchar(char c) 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)]; + return tab[static_cast(c)]; } inline @@ -164,7 +164,7 @@ is_qpchar(char c) 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)]; + return tab[static_cast(c)]; } // converts to lower case, @@ -200,7 +200,7 @@ to_field_char(char c) 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)]; + return tab[static_cast(c)]; } // converts to lower case, @@ -230,9 +230,10 @@ to_value_char(char c) 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)]); + return static_cast(tab[static_cast(c)]); } +// VFALCO TODO Make this return unsigned? inline std::int8_t unhex(char c) @@ -256,22 +257,68 @@ unhex(char c) -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)]; + 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) @@ -403,6 +450,53 @@ increment() } } +/* + #token = [ ( "," / token ) *( OWS "," [ OWS token ] ) ] +*/ +struct opt_token_list_policy +{ + using value_type = boost::string_ref; + + bool + operator()(value_type& v, + char const*& it, boost::string_ref const& 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 = boost::string_ref{&*p0, + static_cast(it - p0)}; + return true; + } + if(c != ',') + return false; + need_comma = false; + ++it; + } + } +}; + } // detail } // http } // beast diff --git a/include/beast/http/empty_body.hpp b/include/beast/http/empty_body.hpp index 40f549d9f4..2e61fb2a3b 100644 --- a/include/beast/http/empty_body.hpp +++ b/include/beast/http/empty_body.hpp @@ -25,14 +25,14 @@ namespace http { */ struct empty_body { -#if GENERATING_DOCS +#if BEAST_DOXYGEN /// The type of the `message::body` member using value_type = void; #else struct value_type {}; #endif -#if GENERATING_DOCS +#if BEAST_DOXYGEN private: #endif diff --git a/include/beast/http/error.hpp b/include/beast/http/error.hpp new file mode 100644 index 0000000000..d273f0e22a --- /dev/null +++ b/include/beast/http/error.hpp @@ -0,0 +1,83 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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 parsing +enum class error +{ + /** The end of the stream was reached. + + This error is returned by @ref basic_parser::write_eof + when the end of stream is reached and there are no + unparsed bytes in the stream buffer. + */ + end_of_stream = 1, + + /** The incoming message is incomplete. + + This happens when the end of stream is reached + and some bytes have been received, but not the + entire message. + */ + partial_message, + + /** 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, + + /// The line ending was malformed + bad_line_ending, + + /// The method is invalid. + bad_method, + + /// The request-target is invalid. + bad_path, + + /// 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 +}; + +} // http +} // beast + +#include + +#endif diff --git a/include/beast/http/header_parser.hpp b/include/beast/http/header_parser.hpp new file mode 100644 index 0000000000..72cdaa0171 --- /dev/null +++ b/include/beast/http/header_parser.hpp @@ -0,0 +1,190 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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_HEADER_PARSER_HPP +#define BEAST_HTTP_HEADER_PARSER_HPP + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** A parser for producing HTTP/1 headers. + + This class uses the basic HTTP/1 wire format parser to convert + a series of octets into a @ref header. + + @note A new instance of the parser is required for each message. + + @tparam isRequest Indicates whether a request or response + will be parsed. + + @tparam Fields The type of container used to represent the fields. +*/ +template +class header_parser + : public basic_parser> +{ + header h_; + +public: + using mutable_buffers_type = + boost::asio::null_buffers; + + /// The type of @ref header this object produces. + using value_type = header; + + /// Copy constructor. + header_parser(header_parser const&) = default; + + /// Copy assignment. + header_parser& operator=(header_parser const&) = default; + + /** Move constructor. + + After the move, the only valid operation + on the moved-from object is destruction. + */ + header_parser(header_parser&&) = default; + + /** Constructor + + @param args If present, additional arguments to be + forwarded to the @ref beast::http::header constructor. + */ + template + explicit + header_parser(Args&&... args); + + /** Returns the parsed header + + Only valid if @ref got_header would return `true`. + */ + value_type const& + get() const + { + return h_; + } + + /** Returns the parsed header. + + Only valid if @ref got_header would return `true`. + */ + value_type& + get() + { + return h_; + } + + /** Returns ownership of the parsed header. + + Ownership is transferred to the caller. Only + valid if @ref got_header would return `true`. + + Requires: + @ref value_type is @b MoveConstructible + */ + value_type + release() + { + static_assert(std::is_move_constructible::value, + "MoveConstructible requirements not met"); + return std::move(h_); + } + +private: + friend class basic_parser< + isRequest, false, header_parser>; + + void + on_request( + boost::string_ref const& method, + boost::string_ref const& path, + int version, error_code&) + { + h_.url = std::string{ + path.data(), path.size()}; + h_.method = std::string{ + method.data(), method.size()}; + h_.version = version; + } + + void + on_response(int status, + boost::string_ref const& reason, + int version, error_code&) + { + h_.status = status; + h_.reason = std::string{ + reason.data(), reason.size()}; + h_.version = version; + } + + void + on_field(boost::string_ref const& name, + boost::string_ref const& value, + error_code&) + { + h_.fields.insert(name, value); + } + + void + on_header(error_code&) + { + } + + void + on_body(error_code& ec) + { + } + + void + on_body(std::uint64_t content_length, + error_code& ec) + { + } + + void + on_data(boost::string_ref const& s, + error_code& ec) + { + } + + void + on_commit(std::size_t n) + { + // Can't write body data with header-only parser! + BOOST_ASSERT(false); + throw std::logic_error{ + "invalid member function call"}; + } + + void + on_chunk(std::uint64_t n, + boost::string_ref const& ext, + error_code& ec) + { + } + + void + on_complete(error_code&) + { + } +}; + +} // 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/async_read.ipp b/include/beast/http/impl/async_read.ipp new file mode 100644 index 0000000000..af362f6b97 --- /dev/null +++ b/include/beast/http/impl/async_read.ipp @@ -0,0 +1,716 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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_ASYNC_READ_IPP_HPP +#define BEAST_HTTP_IMPL_ASYNC_READ_IPP_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { +namespace detail { + +template +class read_some_buffer_op +{ + struct data + { + bool cont; + Stream& s; + DynamicBuffer& db; + basic_parser& p; + boost::optional mb; + boost::optional bb; + std::size_t bytes_used; + int state = 0; + + data(Handler& handler, Stream& s_, DynamicBuffer& db_, + basic_parser& p_) + : cont(beast_asio_helpers:: + is_continuation(handler)) + , s(s_) + , db(db_) + , p(p_) + { + } + }; + + handler_ptr d_; + +public: + read_some_buffer_op(read_some_buffer_op&&) = default; + read_some_buffer_op(read_some_buffer_op const&) = default; + + template + read_some_buffer_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, + read_some_buffer_op* op) + { + return beast_asio_helpers:: + allocate(size, op->d_.handler()); + } + + friend + void + asio_handler_deallocate( + void* p, std::size_t size, + read_some_buffer_op* op) + { + return beast_asio_helpers:: + deallocate(p, size, op->d_.handler()); + } + + friend + bool + asio_handler_is_continuation( + read_some_buffer_op* op) + { + return op->d_->cont; + } + + template + friend + void + asio_handler_invoke(Function&& f, + read_some_buffer_op* op) + { + return beast_asio_helpers:: + invoke(f, op->d_.handler()); + } +}; + +template +void +read_some_buffer_op:: +operator()(error_code ec, + std::size_t bytes_transferred, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + if(d.state == 99) + goto upcall; + for(;;) + { + switch(d.state) + { + case 0: + if(d.db.size() == 0) + { + d.state = 2; + break; + } + //[[fallthrough]] + + case 1: + { + BOOST_ASSERT(d.db.size() > 0); + d.bytes_used = + d.p.write(d.db.data(), ec); + if(d.bytes_used > 0 || ec) + { + // call handler + if(d.state == 1) + goto upcall; + d.state = 99; + d.s.get_io_service().post( + bind_handler(std::move(*this), ec, 0)); + return; + } + //[[fallthrough]] + } + + case 2: + case 3: + { + auto const size = + read_size_helper(d.db, 65536); + BOOST_ASSERT(size > 0); + try + { + d.mb.emplace(d.db.prepare(size)); + } + catch(std::length_error const&) + { + // call handler + if(d.state == 3) + goto upcall; + d.state = 99; + d.s.get_io_service().post( + bind_handler(std::move(*this), + error::buffer_overflow, 0)); + return; + } + // read + d.state = 4; + d.s.async_read_some(*d.mb, std::move(*this)); + return; + } + + case 4: + if(ec == boost::asio::error::eof) + { + BOOST_ASSERT(bytes_transferred == 0); + d.bytes_used = 0; + if(! d.p.got_some()) + goto upcall; + // caller sees EOF on next read. + ec = {}; + d.p.write_eof(ec); + if(ec) + goto upcall; + BOOST_ASSERT(d.p.is_complete()); + goto upcall; + } + else if(ec) + { + d.bytes_used = 0; + goto upcall; + } + BOOST_ASSERT(bytes_transferred > 0); + d.db.commit(bytes_transferred); + d.state = 1; + break; + } + } +upcall: + // can't pass any members of `d` otherwise UB + auto const bytes_used = d.bytes_used; + d_.invoke(ec, bytes_used); +} + +//------------------------------------------------------------------------------ + +template +class read_some_body_op +{ + struct data + { + bool cont; + Stream& s; + DynamicBuffer& db; + basic_parser& p; + boost::optional mb; + std::size_t bytes_used; + int state = 0; + + data(Handler& handler, Stream& s_, DynamicBuffer& db_, + basic_parser& p_) + : cont(beast_asio_helpers:: + is_continuation(handler)) + , s(s_) + , db(db_) + , p(p_) + { + } + }; + + handler_ptr d_; + +public: + read_some_body_op(read_some_body_op&&) = default; + read_some_body_op(read_some_body_op const&) = default; + + template + read_some_body_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, + read_some_body_op* op) + { + return beast_asio_helpers:: + allocate(size, op->d_.handler()); + } + + friend + void + asio_handler_deallocate( + void* p, std::size_t size, + read_some_body_op* op) + { + return beast_asio_helpers:: + deallocate(p, size, op->d_.handler()); + } + + friend + bool + asio_handler_is_continuation( + read_some_body_op* op) + { + return op->d_->cont; + } + + template + friend + void + asio_handler_invoke(Function&& f, + read_some_body_op* op) + { + return beast_asio_helpers:: + invoke(f, op->d_.handler()); + } +}; + +template +void +read_some_body_op:: +operator()(error_code ec, + std::size_t bytes_transferred, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + if(d.state == 99) + goto upcall; + for(;;) + { + switch(d.state) + { + case 0: + if(d.db.size() > 0) + { + d.bytes_used = d.p.copy_body(d.db); + // call handler + d.state = 99; + d.s.get_io_service().post( + bind_handler(std::move(*this), + ec, 0)); + return; + } + d.p.prepare_body(d.mb, 65536); + // read + d.state = 1; + d.s.async_read_some( + *d.mb, std::move(*this)); + return; + + case 1: + d.bytes_used = 0; + if(ec == boost::asio::error::eof) + { + BOOST_ASSERT(bytes_transferred == 0); + // caller sees EOF on next read + ec = {}; + d.p.write_eof(ec); + if(ec) + goto upcall; + BOOST_ASSERT(d.p.is_complete()); + } + else if(! ec) + { + d.p.commit_body(bytes_transferred); + } + goto upcall; + } + } +upcall: + // can't pass any members of `d` otherwise UB + auto const bytes_used = d.bytes_used; + d_.invoke(ec, bytes_used); +} + +//------------------------------------------------------------------------------ + +template +class parse_op +{ + struct data + { + bool cont; + Stream& s; + DynamicBuffer& db; + basic_parser& p; + + data(Handler& handler, Stream& s_, DynamicBuffer& db_, + basic_parser& p_) + : cont(beast_asio_helpers:: + is_continuation(handler)) + , s(s_) + , db(db_) + , p(p_) + { + } + }; + + 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 const& ec, + std::size_t bytes_used, 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 const& ec, + std::size_t bytes_used, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + if(! ec) + { + d.db.consume(bytes_used); + if(! d.p.is_complete()) + return async_read_some( + d.s, d.db, d.p, std::move(*this)); + } + d_.invoke(ec); +} + +//------------------------------------------------------------------------------ + +template +class read_message_op +{ + using parser_type = + message_parser; + + 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_; + +public: + read_message_op(read_message_op&&) = default; + read_message_op(read_message_op const&) = default; + + template + read_message_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); + + friend + void* asio_handler_allocate( + std::size_t size, read_message_op* op) + { + return beast_asio_helpers:: + allocate(size, op->d_.handler()); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, read_message_op* op) + { + return beast_asio_helpers:: + deallocate(p, size, op->d_.handler()); + } + + friend + bool asio_handler_is_continuation(read_message_op* op) + { + return op->d_->cont; + } + + template + friend + void asio_handler_invoke(Function&& f, read_message_op* op) + { + return beast_asio_helpers:: + invoke(f, op->d_.handler()); + } +}; + +template +void +read_message_op:: +operator()(error_code ec, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + if(ec) + goto upcall; + switch(d.state) + { + case 0: + d.state = 1; + beast::http::async_read( + d.s, d.db, d.p, std::move(*this)); + return; + + case 1: + d.m = d.p.release(); + goto upcall; + } +upcall: + d_.invoke(ec); +} + +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived, + class ReadHandler> +typename async_completion< + ReadHandler, void(error_code, std::size_t)>::result_type +async_read_some( + AsyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + ReadHandler&& handler) +{ + beast::async_completion completion{handler}; + switch(parser.state()) + { + case parse_state::header: + case parse_state::chunk_header: + detail::read_some_buffer_op{ + completion.handler, stream, dynabuf, parser}; + break; + + default: + detail::read_some_body_op{ + completion.handler, stream, dynabuf, parser}; + break; + } + return completion.result.get(); +} + +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived, + class ReadHandler> +inline +typename async_completion< + ReadHandler, void(error_code, std::size_t)>::result_type +async_read_some( + AsyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + ReadHandler&& handler) +{ + beast::async_completion completion{handler}; + detail::read_some_buffer_op{ + completion.handler, stream, dynabuf, parser}; + return completion.result.get(); +} + +} // detail + +//------------------------------------------------------------------------------ + +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, bool isDirect, class Derived, + class ReadHandler> +typename async_completion< + ReadHandler, void(error_code, std::size_t)>::result_type +async_read_some( + AsyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + ReadHandler&& handler) +{ + static_assert(is_AsyncReadStream::value, + "AsyncReadStream requirements not met"); + static_assert(is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); + BOOST_ASSERT(! parser.is_complete()); + return detail::async_read_some(stream, dynabuf, parser, + std::forward(handler)); +} + +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, bool isDirect, class Derived, + class ReadHandler> +typename async_completion< + ReadHandler, void(error_code)>::result_type +async_read( + AsyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + ReadHandler&& handler) +{ + static_assert(is_AsyncReadStream::value, + "AsyncReadStream requirements not met"); + static_assert(is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); + BOOST_ASSERT(! parser.is_complete()); + beast::async_completion completion{handler}; + detail::parse_op{ + completion.handler, stream, dynabuf, parser}; + return completion.result.get(); +} + +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Body, class Fields, + class ReadHandler> +typename async_completion< + ReadHandler, void(error_code)>::result_type +async_read( + AsyncReadStream& stream, + DynamicBuffer& dynabuf, + message& msg, + ReadHandler&& handler) +{ + static_assert(is_AsyncReadStream::value, + "AsyncReadStream requirements not met"); + static_assert(is_DynamicBuffer::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"); + beast::async_completion completion{handler}; + detail::read_message_op{completion.handler, + stream, dynabuf, msg}; + return completion.result.get(); +} + +} // 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..b7e5ba4b79 --- /dev/null +++ b/include/beast/http/impl/basic_parser.ipp @@ -0,0 +1,1062 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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 + +namespace beast { +namespace http { + +template +template +basic_parser:: +basic_parser(basic_parser&& other) + : len_(other.len_) + , buf_(std::move(other.buf_)) + , buf_len_(other.buf_len_) + , skip_(other.skip_) + , x_(other.x_) + , f_(other.f_) + , state_(other.state_) +{ +} + +template +void +basic_parser:: +skip_body() +{ + BOOST_ASSERT(! got_some()); + f_ |= flagSkipBody; +} + +template +bool +basic_parser:: +is_keep_alive() const +{ + BOOST_ASSERT(got_header()); + if(f_ & flagHTTP11) + { + if(f_ & flagConnectionClose) + return false; + } + else + { + if(! (f_ & flagConnectionKeepAlive)) + return false; + } + return (f_ & flagNeedEOF) == 0; +} + +template +template +std::size_t +basic_parser:: +write(ConstBufferSequence const& buffers, + error_code& ec) +{ + static_assert(is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + auto const buffer = maybe_flatten(buffers); + return write(boost::asio::const_buffers_1{ + buffer.data(), buffer.size()}, ec); +} + +template +std::size_t +basic_parser:: +write(boost::asio::const_buffers_1 const& buffer, + error_code& ec) +{ + return do_write(buffer, ec, + std::integral_constant{}); +} + +template +void +basic_parser:: +write_eof(error_code& ec) +{ + BOOST_ASSERT(got_some()); + if(state_ == parse_state::header) + { + ec = error::partial_message; + return; + } + if(f_ & (flagContentLength | flagChunked)) + { + if(state_ != parse_state::complete) + { + ec = error::partial_message; + return; + } + return; + } + do_complete(ec); + if(ec) + return; +} + +template +template +std::size_t +basic_parser:: +copy_body(DynamicBuffer& dynabuf) +{ + // This function not available when isDirect==false + static_assert(isDirect, ""); + + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + BOOST_ASSERT(dynabuf.size() > 0); + BOOST_ASSERT( + state_ == parse_state::body || + state_ == parse_state::body_to_eof || + state_ == parse_state::chunk_body); + maybe_do_body_direct(); + switch(state_) + { + case parse_state::body_to_eof: + { + auto const buffers = + impl().on_prepare(dynabuf.size()); + BOOST_ASSERT( + buffer_size(buffers) >= 1 && + buffer_size(buffers) <= + dynabuf.size()); + auto const n = buffer_copy( + buffers, dynabuf.data()); + dynabuf.consume(n); + impl().on_commit(n); + return n; + } + + default: + { + BOOST_ASSERT(len_ > 0); + auto const buffers = + impl().on_prepare( + beast::detail::clamp(len_)); + BOOST_ASSERT( + buffer_size(buffers) >= 1 && + buffer_size(buffers) <= + beast::detail::clamp(len_)); + auto const n = buffer_copy( + buffers, dynabuf.data()); + commit_body(n); + return n; + } + } +} + +template +template +void +basic_parser:: +prepare_body(boost::optional< + MutableBufferSequence>& buffers, std::size_t limit) +{ + // This function not available when isDirect==false + static_assert(isDirect, ""); + + BOOST_ASSERT(limit > 0); + BOOST_ASSERT( + state_ == parse_state::body || + state_ == parse_state::body_to_eof || + state_ == parse_state::chunk_body); + maybe_do_body_direct(); + std::size_t n; + switch(state_) + { + case parse_state::body_to_eof: + n = limit; + break; + + default: + BOOST_ASSERT(len_ > 0); + n = beast::detail::clamp(len_, limit); + break; + } + buffers.emplace(impl().on_prepare(n)); +} + +template +void +basic_parser:: +commit_body(std::size_t n) +{ + // This function not available when isDirect==false + static_assert(isDirect, ""); + + BOOST_ASSERT(f_ & flagOnBody); + impl().on_commit(n); + switch(state_) + { + case parse_state::body: + len_ -= n; + if(len_ == 0) + { + // VFALCO This is no good, throwing out ec? + error_code ec; + do_complete(ec); + } + break; + + case parse_state::chunk_body: + len_ -= n; + if(len_ == 0) + state_ = parse_state::chunk_header; + break; + + default: + break; + } +} + +template +void +basic_parser:: +consume_body(error_code& ec) +{ + BOOST_ASSERT( + state_ == parse_state::body || + state_ == parse_state::body_to_eof || + state_ == parse_state::chunk_body); + switch(state_) + { + case parse_state::body: + case parse_state::body_to_eof: + do_complete(ec); + if(ec) + return; + break; + + case parse_state::chunk_body: + len_ = 0; + state_ = parse_state::chunk_header; + break; + + default: + break; + } +} + +template +template +inline +boost::string_ref +basic_parser:: +maybe_flatten( + ConstBufferSequence const& buffers) +{ + using boost::asio::buffer; + using boost::asio::buffer_cast; + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + + auto const it = buffers.begin(); + auto const last = buffers.end(); + if(it == last) + return {nullptr, 0}; + if(std::next(it) == last) + { + // single buffer + auto const b = *it; + return {buffer_cast(b), + buffer_size(b)}; + } + auto const len = buffer_size(buffers); + if(len > buf_len_) + { + // reallocate + buf_.reset(new char[len]); + buf_len_ = len; + } + // flatten + buffer_copy( + buffer(buf_.get(), buf_len_), buffers); + return {buf_.get(), buf_len_}; +} + +template +inline +std::size_t +basic_parser:: +do_write(boost::asio::const_buffers_1 const& buffer, + error_code& ec, std::true_type) +{ + BOOST_ASSERT( + state_ == parse_state::header || + state_ == parse_state::chunk_header); + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + auto const p = buffer_cast< + char const*>(*buffer.begin()); + auto const n = + buffer_size(*buffer.begin()); + if(state_ == parse_state::header) + { + if(n > 0) + f_ |= flagGotSome; + return parse_header(p, n, ec); + } + else + { + maybe_do_body_direct(); + return parse_chunk_header(p, n, ec); + } +} + +template +inline +std::size_t +basic_parser:: +do_write(boost::asio::const_buffers_1 const& buffer, + error_code& ec, std::false_type) +{ + BOOST_ASSERT(state_ != parse_state::complete); + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + auto const p = buffer_cast< + char const*>(*buffer.begin()); + auto const n = + buffer_size(*buffer.begin()); + switch(state_) + { + case parse_state::header: + if(n > 0) + f_ |= flagGotSome; + return parse_header(p, n, ec); + + case parse_state::body: + maybe_do_body_indirect(ec); + if(ec) + return 0; + return parse_body(p, n, ec); + + case parse_state::body_to_eof: + maybe_do_body_indirect(ec); + if(ec) + return 0; + return parse_body_to_eof(p, n, ec); + + case parse_state::chunk_header: + maybe_do_body_indirect(ec); + if(ec) + return 0; + return parse_chunk_header(p, n, ec); + + case parse_state::chunk_body: + return parse_chunk_body(p, n, ec); + + case parse_state::complete: + break; + } + return 0; +} + + +template +void +basic_parser:: +parse_startline(char const*& it, + int& version, int& status, + error_code& ec, std::true_type) +{ +/* + request-line = method SP request-target SP HTTP-version CRLF + method = token +*/ + auto const method = parse_method(it); + if(method.empty()) + { + ec = error::bad_method; + return; + } + if(*it++ != ' ') + { + ec = error::bad_method; + return; + } + + auto const path = parse_path(it); + if(path.empty()) + { + ec = error::bad_path; + return; + } + if(*it++ != ' ') + { + ec = error::bad_path; + return; + } + + version = parse_version(it); + if(version < 0 || ! parse_crlf(it)) + { + ec = error::bad_version; + return; + } + + impl().on_request( + method, path, version, ec); + if(ec) + return; +} + +template +void +basic_parser:: +parse_startline(char const*& it, + int& version, int& status, + 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 ) +*/ + version = parse_version(it); + if(version < 0 || *it != ' ') + { + ec = error::bad_version; + return; + } + ++it; + + status = parse_status(it); + if(status < 0 || *it != ' ') + { + ec = error::bad_status; + return; + } + ++it; + + auto const reason = parse_reason(it); + if(! parse_crlf(it)) + { + ec = error::bad_reason; + return; + } + + impl().on_response( + status, reason, version, ec); + if(ec) + return; +} + +template +void +basic_parser:: +parse_fields(char const*& it, + char const* last, 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 +*/ + for(;;) + { + auto term = find_eol(it, last, ec); + if(ec) + return; + BOOST_ASSERT(term); + if(it == term - 2) + { + it = term; + break; + } + auto const name = parse_name(it); + if(name.empty()) + { + ec = error::bad_field; + return; + } + if(*it++ != ':') + { + ec = error::bad_field; + return; + } + if(*term != ' ' && + *term != '\t') + { + auto it2 = term - 2; + detail::skip_ows(it, it2); + detail::skip_ows_rev(it2, it); + auto const value = + make_string(it, it2); + do_field(name, value, ec); + if(ec) + return; + impl().on_field(name, value, ec); + if(ec) + return; + it = term; + } + else + { + // obs-fold + for(;;) + { + auto const it2 = term - 2; + detail::skip_ows(it, it2); + if(it != it2) + break; + it = term; + if(*it != ' ' && *it != '\t') + break; + term = find_eol(it, last, ec); + if(ec) + return; + } + std::string s; + if(it != term) + { + s.append(it, term - 2); + it = term; + for(;;) + { + if(*it != ' ' && *it != '\t') + break; + s.push_back(' '); + detail::skip_ows(it, term - 2); + term = find_eol(it, last, ec); + if(ec) + return; + if(it != term - 2) + s.append(it, term - 2); + it = term; + } + } + boost::string_ref value{ + s.data(), s.size()}; + do_field(name, value, ec); + if(ec) + return; + impl().on_field(name, value, ec); + if(ec) + return; + } + } +} + +template +void +basic_parser:: +do_field( + boost::string_ref const& name, + boost::string_ref const& value, + error_code& ec) +{ + // Connection + if(strieq("connection", name) || + strieq("proxy-connection", name)) + { + 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(strieq("close", s)) + { + f_ |= flagConnectionClose; + continue; + } + + if(strieq("keep-alive", s)) + { + f_ |= flagConnectionKeepAlive; + continue; + } + + if(strieq("upgrade", s)) + { + f_ |= flagConnectionUpgrade; + continue; + } + } + return; + } + + for(auto it = value.begin(); + it != value.end(); ++it) + { + if(! is_text(*it)) + { + ec = error::bad_value; + return; + } + } + + // Content-Length + if(strieq("content-length", name)) + { + 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; + } + + len_ = v; + f_ |= flagContentLength; + return; + } + + // Transfer-Encoding + if(strieq("transfer-encoding", name)) + { + if(f_ & flagChunked) + { + // duplicate + ec = error::bad_transfer_encoding; + return; + } + + if(f_ & flagContentLength) + { + // conflicting field + ec = error::bad_transfer_encoding; + return; + } + + auto const v = token_list{value}; + auto const it = std::find_if(v.begin(), v.end(), + [&](typename token_list::value_type const& s) + { + return strieq("chunked", s); + }); + if(it == v.end()) + return; + if(std::next(it) != v.end()) + return; + len_ = 0; + f_ |= flagChunked; + return; + } + + // Upgrade + if(strieq("upgrade", name)) + { + f_ |= flagUpgrade; + ec = {}; + return; + } +} + +template +inline +std::size_t +basic_parser:: +parse_header(char const* p, + std::size_t n, error_code& ec) +{ + if(n < 4) + return 0; + auto const term = find_eom( + p + skip_, p + n, ec); + if(ec) + return 0; + if(! term) + { + skip_ = n - 3; + return 0; + } + + int version; + int status; // ignored for requests + + skip_ = 0; + n = term - p; + parse_startline(p, version, status, ec, + std::integral_constant< + bool, isRequest>{}); + if(ec) + return 0; + if(version >= 11) + f_ |= flagHTTP11; + + parse_fields(p, term, ec); + if(ec) + return 0; + BOOST_ASSERT(p == term); + + do_header(status, + std::integral_constant< + bool, isRequest>{}); + impl().on_header(ec); + if(ec) + return 0; + if(state_ == parse_state::complete) + { + impl().on_complete(ec); + if(ec) + return 0; + } + return n; +} + +template +void +basic_parser:: +do_header(int, std::true_type) +{ + // RFC 7230 section 3.3 + // https://tools.ietf.org/html/rfc7230#section-3.3 + + if(f_ & flagSkipBody) + { + state_ = parse_state::complete; + } + else if(f_ & flagContentLength) + { + if(len_ > 0) + { + f_ |= flagHasBody; + state_ = parse_state::body; + } + else + { + state_ = parse_state::complete; + } + } + else if(f_ & flagChunked) + { + f_ |= flagHasBody; + state_ = parse_state::chunk_header; + } + else + { + len_ = 0; + state_ = parse_state::complete; + } +} + +template +void +basic_parser:: +do_header(int status, 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_ = parse_state::complete; + return; + } + + if(f_ & flagContentLength) + { + if(len_ > 0) + { + f_ |= flagHasBody; + state_ = parse_state::body; + } + else + { + state_ = parse_state::complete; + } + } + else if(f_ & flagChunked) + { + f_ |= flagHasBody; + state_ = parse_state::chunk_header; + } + else + { + f_ |= flagHasBody; + f_ |= flagNeedEOF; + state_ = parse_state::body_to_eof; + } +} + +template +void +basic_parser:: +maybe_do_body_direct() +{ + if(f_ & flagOnBody) + return; + f_ |= flagOnBody; + if(got_content_length()) + impl().on_body(len_); + else + impl().on_body(); +} + +template +void +basic_parser:: +maybe_do_body_indirect(error_code& ec) +{ + if(f_ & flagOnBody) + return; + f_ |= flagOnBody; + if(got_content_length()) + { + impl().on_body(len_, ec); + if(ec) + return; + } + else + { + impl().on_body(ec); + if(ec) + return; + } +} + +template +std::size_t +basic_parser:: +parse_chunk_header(char const* p, + 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 const first = p; + auto const last = p + n; + + // Treat the last CRLF in a chunk as + // part of the next chunk, so it can + // be parsed in one call instead of two. + if(f_ & flagExpectCRLF) + { + if(n < 2) + return 0; + if(! parse_crlf(p)) + { + ec = error::bad_chunk; + return 0; + } + n -= 2; + } + + char const* term; + + if(! (f_ & flagFinalChunk)) + { + if(n < 2) + return 0; + term = find_eol(p + skip_, last, ec); + if(ec) + return 0; + if(! term) + { + skip_ = n - 1; + return 0; + } + std::uint64_t v; + if(! parse_hex(p, v)) + { + ec = error::bad_chunk; + return 0; + } + if(v != 0) + { + if(*p == ';') + { + // VFALCO We need to parse the chunk + // extension to validate it here. + ext_ = make_string(p, term - 2); + impl().on_chunk(v, ext_, ec); + if(ec) + return 0; + } + else if(p != term - 2) + { + ec = error::bad_chunk; + return 0; + } + p = term; + len_ = v; + skip_ = 0; + f_ |= flagExpectCRLF; + state_ = parse_state::chunk_body; + return p - first; + } + + // This is the offset from the buffer + // to the beginning of the first '\r\n' + x_ = term - 2 - first; + skip_ = x_; + + f_ |= flagFinalChunk; + } + else + { + // We are parsing the value again + // to advance p to the right place. + std::uint64_t v; + auto const result = parse_hex(p, v); + BOOST_ASSERT(result && v == 0); + beast::detail::ignore_unused(result); + beast::detail::ignore_unused(v); + } + + term = find_eom( + first + skip_, last, ec); + if(ec) + return 0; + if(! term) + { + if(n > 3) + skip_ = (last - first) - 3; + return 0; + } + + if(*p == ';') + { + ext_ = make_string(p, first + x_); + impl().on_chunk(0, ext_, ec); + if(ec) + return 0; + p = first + x_; + } + if(! parse_crlf(p)) + { + ec = error::bad_chunk; + return 0; + } + parse_fields(p, term, ec); + if(ec) + return 0; + BOOST_ASSERT(p == term); + + do_complete(ec); + if(ec) + return 0; + return p - first; +} + +template +inline +std::size_t +basic_parser:: +parse_body(char const* p, + std::size_t n, error_code& ec) +{ + n = beast::detail::clamp(len_, n); + body_ = boost::string_ref{p, n}; + impl().on_data(body_, ec); + if(ec) + return 0; + len_ -= n; + if(len_ == 0) + { + do_complete(ec); + if(ec) + return 0; + } + return n; +} + +template +inline +std::size_t +basic_parser:: +parse_body_to_eof(char const* p, + std::size_t n, error_code& ec) +{ + body_ = boost::string_ref{p, n}; + impl().on_data(body_, ec); + if(ec) + return 0; + return n; +} + +template +inline +std::size_t +basic_parser:: +parse_chunk_body(char const* p, + std::size_t n, error_code& ec) +{ + n = beast::detail::clamp(len_, n); + body_ = boost::string_ref{p, n}; + impl().on_data(body_, ec); + if(ec) + return 0; + len_ -= n; + if(len_ == 0) + { + body_ = {}; + state_ = parse_state::chunk_header; + } + return n; +} + +template +void +basic_parser:: +do_complete(error_code& ec) +{ + impl().on_complete(ec); + if(ec) + return; + state_ = parse_state::complete; +} + +} // 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..579663ffb6 --- /dev/null +++ b/include/beast/http/impl/error.ipp @@ -0,0 +1,106 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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 "http"; + } + + std::string + message(int ev) const override + { + switch(static_cast(ev)) + { + default: + case error::end_of_stream: return "end of stream"; + case error::partial_message: return "partial message"; + case error::buffer_overflow: return "buffer overflow"; + case error::bad_line_ending: return "bad line ending"; + case error::bad_method: return "bad method"; + case error::bad_path: return "bad path"; + 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"; + } + } + + 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/header_parser.ipp b/include/beast/http/impl/header_parser.ipp new file mode 100644 index 0000000000..5892564ab5 --- /dev/null +++ b/include/beast/http/impl/header_parser.ipp @@ -0,0 +1,25 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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_HEADER_PARSER_IPP +#define BEAST_HTTP_IMPL_HEADER_PARSER_IPP + +namespace beast { +namespace http { + +template +template +header_parser:: +header_parser(Args&&... args) + : h_(std::forward(args)...) +{ +} + +} // http +} // beast + +#endif diff --git a/include/beast/http/impl/message_parser.ipp b/include/beast/http/impl/message_parser.ipp new file mode 100644 index 0000000000..276c6a4a0b --- /dev/null +++ b/include/beast/http/impl/message_parser.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_HTTP_IMPL_MESSAGE_PARSER_IPP +#define BEAST_HTTP_IMPL_MESSAGE_PARSER_IPP + +namespace beast { +namespace http { + +template +template +message_parser:: +message_parser(Arg1&& arg1, ArgN&&... argn) + : m_(std::forward(arg1), + std::forward(argn)...) +{ +} + +template +template +message_parser:: +message_parser(header_parser< + isRequest, Fields>&& parser, Args&&... args) + : base_type(std::move(static_cast>&>(parser))) + , m_(parser.release(), std::forward(args)...) +{ +} + +} // http +} // beast + +#endif 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/read.ipp b/include/beast/http/impl/read.ipp index 3bcf27c7b8..e14b5dd90e 100644 --- a/include/beast/http/impl/read.ipp +++ b/include/beast/http/impl/read.ipp @@ -9,306 +9,275 @@ #define BEAST_HTTP_IMPL_READ_IPP_HPP #include -#include -#include -#include +#include +#include +#include #include #include #include #include #include +#include namespace beast { namespace http { namespace detail { -template -class read_header_op +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, bool isDirect, class Derived> +inline +std::size_t +read_some_buffer( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + error_code& ec) { - using parser_type = - header_parser_v1; - - using message_type = - header; - - struct data + std::size_t bytes_used; + if(dynabuf.size() == 0) + goto do_read; + for(;;) { - 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_) + bytes_used = parser.write( + dynabuf.data(), ec); + if(ec) + return 0; + if(bytes_used > 0) + goto do_finish; + do_read: + boost::optional mb; + auto const size = + read_size_helper(dynabuf, 65536); + BOOST_ASSERT(size > 0); + try { + mb.emplace(dynabuf.prepare(size)); } - }; - - handler_ptr d_; - -public: - read_header_op(read_header_op&&) = default; - read_header_op(read_header_op const&) = default; - - template - read_header_op( - DeducedHandler&& h, Stream& s, Args&&... args) - : d_(std::forward(h), - s, std::forward(args)...) - { - (*this)(error_code{}, false); + catch(std::length_error const&) + { + ec = error::buffer_overflow; + return 0; + } + auto const bytes_transferred = + stream.read_some(*mb, ec); + if(ec == boost::asio::error::eof) + { + BOOST_ASSERT(bytes_transferred == 0); + bytes_used = 0; + if(parser.got_some()) + { + // caller sees EOF on next read + ec = {}; + parser.write_eof(ec); + if(ec) + return 0; + BOOST_ASSERT(parser.is_complete()); + } + break; + } + else if(ec) + { + return 0; + } + BOOST_ASSERT(bytes_transferred > 0); + dynabuf.commit(bytes_transferred); } +do_finish: + return bytes_used; +} - void - operator()(error_code ec, bool again = true); - - friend - void* asio_handler_allocate( - std::size_t size, read_header_op* op) +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +inline +std::size_t +read_some_body( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + error_code& ec) +{ + if(dynabuf.size() > 0) + return parser.copy_body(dynabuf); + boost::optional mb; + try { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); + parser.prepare_body(mb, 65536); } - - friend - void asio_handler_deallocate( - void* p, std::size_t size, read_header_op* op) + catch(std::length_error const&) { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); + ec = error::buffer_overflow; + return 0; } - - friend - bool asio_handler_is_continuation(read_header_op* op) + auto const bytes_transferred = + stream.read_some(*mb, ec); + if(ec == boost::asio::error::eof) { - return op->d_->cont; + BOOST_ASSERT(bytes_transferred == 0); + // caller sees EOF on next read + ec = {}; + parser.write_eof(ec); + if(ec) + return 0; + BOOST_ASSERT(parser.is_complete()); } - - template - friend - void asio_handler_invoke(Function&& f, read_header_op* op) + else if(! ec) { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); + parser.commit_body(bytes_transferred); + return 0; } -}; + return 0; +} -template -void -read_header_op:: -operator()(error_code ec, bool again) +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +inline +std::size_t +read_some( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + error_code& ec) { - auto& d = *d_; - d.cont = d.cont || again; - while(! ec && d.state != 99) + switch(parser.state()) { - switch(d.state) - { - 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; - } + case parse_state::header: + case parse_state::chunk_header: + return detail::read_some_buffer( + stream, dynabuf, parser, ec); + + default: + return detail::read_some_body( + stream, dynabuf, parser, ec); } - d_.invoke(ec); +} + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +inline +std::size_t +read_some( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + error_code& ec) +{ + return detail::read_some_buffer( + stream, dynabuf, parser, ec); } } // detail -template -void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - header& msg) +//------------------------------------------------------------------------------ + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, bool isDirect, class Derived> +std::size_t +read_some( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser) { static_assert(is_SyncReadStream::value, "SyncReadStream requirements not met"); static_assert(is_DynamicBuffer::value, "DynamicBuffer requirements not met"); + BOOST_ASSERT(! parser.is_complete()); error_code ec; - beast::http::read(stream, dynabuf, msg, ec); + auto const bytes_used = + read_some(stream, dynabuf, parser, ec); if(ec) throw system_error{ec}; + return bytes_used; } -template -void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - header& m, - error_code& ec) +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, bool isDirect, class Derived> +std::size_t +read_some( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + 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(); + BOOST_ASSERT(! parser.is_complete()); + return detail::read_some(stream, dynabuf, parser, ec); } -template -typename async_completion< - ReadHandler, void(error_code)>::result_type -async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf, - header& m, - ReadHandler&& handler) +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, bool isDirect, class Derived> +void +read( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser) { - static_assert(is_AsyncReadStream::value, - "AsyncReadStream requirements not met"); + static_assert(is_SyncReadStream::value, + "SyncReadStream 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(); + BOOST_ASSERT(! parser.is_complete()); + error_code ec; + read(stream, dynabuf, parser, ec); + if(ec) + throw system_error{ec}; } -//------------------------------------------------------------------------------ - -namespace detail { - -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_; - -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)...) - { - (*this)(error_code{}, false); - } - - void - operator()(error_code ec, bool again = true); - - friend - void* asio_handler_allocate( - std::size_t size, read_op* op) - { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); - } - - friend - void asio_handler_deallocate( - void* p, std::size_t size, read_op* op) - { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); - } - - friend - bool asio_handler_is_continuation(read_op* op) - { - return op->d_->cont; - } - - template - friend - void asio_handler_invoke(Function&& f, read_op* op) - { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); - } -}; - -template +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, bool isDirect, class Derived> void -read_op:: -operator()(error_code ec, bool again) +read( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + error_code& ec) { - auto& d = *d_; - d.cont = d.cont || again; - while(! ec && d.state != 99) + static_assert(is_SyncReadStream::value, + "SyncReadStream requirements not met"); + static_assert(is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); + BOOST_ASSERT(! parser.is_complete()); + do { - switch(d.state) - { - case 0: - d.state = 1; - async_parse(d.s, d.db, d.p, std::move(*this)); + auto const bytes_used = + read_some(stream, dynabuf, parser, ec); + if(ec) return; - - case 1: - // call handler - d.state = 99; - d.m = d.p.release(); - break; - } + dynabuf.consume(bytes_used); } - d_.invoke(ec); + while(! parser.is_complete()); } -} // detail - -template void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, +read( + SyncReadStream& stream, + DynamicBuffer& dynabuf, message& msg) { static_assert(is_SyncReadStream::value, @@ -328,12 +297,16 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf, throw system_error{ec}; } -template void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - message& m, - error_code& ec) +read( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + message& msg, + error_code& ec) { static_assert(is_SyncReadStream::value, "SyncReadStream requirements not met"); @@ -346,41 +319,11 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf, static_assert(is_Reader>::value, "Reader requirements not met"); - parser_v1 p; - beast::http::parse(stream, dynabuf, p, ec); + message_parser p; + beast::http::read(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, - message& m, - ReadHandler&& handler) -{ - static_assert(is_AsyncReadStream::value, - "AsyncReadStream requirements not met"); - static_assert(is_DynamicBuffer::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"); - beast::async_completion completion{handler}; - detail::read_op{completion.handler, - stream, dynabuf, m}; - return completion.result.get(); + msg = p.release(); } } // http diff --git a/include/beast/http/impl/rfc7230.ipp b/include/beast/http/impl/rfc7230.ipp index 5ad65c28d1..c31638fcad 100644 --- a/include/beast/http/impl/rfc7230.ipp +++ b/include/beast/http/impl/rfc7230.ipp @@ -542,6 +542,26 @@ exists(T const& s) ) != 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/message.hpp b/include/beast/http/message.hpp index 70cfbf19c1..ae47d633e3 100644 --- a/include/beast/http/message.hpp +++ b/include/beast/http/message.hpp @@ -19,7 +19,7 @@ namespace beast { namespace http { -#if GENERATING_DOCS +#if BEAST_DOXYGEN /** A container for a HTTP request or response header. A header includes the Start Line and Fields. @@ -44,7 +44,7 @@ struct header #endif { /// Indicates if the header is a request or response. -#if GENERATING_DOCS +#if BEAST_DOXYGEN static bool constexpr is_request = isRequest; #else @@ -104,7 +104,7 @@ struct header if and only if the first parameter is not convertible to `header`. */ -#if GENERATING_DOCS +#if BEAST_DOXYGEN template explicit header(Args&&... args); @@ -293,7 +293,7 @@ struct message : header only if `u` is not convertible to `base_type`. */ template::type, base_type>::value>::type @@ -315,7 +315,7 @@ struct message : header only if `u` is not convertible to `base_type`. */ template::type, base_type>::value>::type #endif @@ -388,7 +388,7 @@ struct message : header //------------------------------------------------------------------------------ -#if GENERATING_DOCS +#if BEAST_DOXYGEN /** Swap two header objects. @par Requirements diff --git a/include/beast/http/message_parser.hpp b/include/beast/http/message_parser.hpp new file mode 100644 index 0000000000..2c3a16e8c9 --- /dev/null +++ b/include/beast/http/message_parser.hpp @@ -0,0 +1,286 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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_MESSAGE_PARSER_HPP +#define BEAST_HTTP_MESSAGE_PARSER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** 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 @ref message. + + @tparam isRequest Indicates whether a request or response + will be parsed. + + @tparam Body The type used to represent the body. + + @tparam Fields The type of container used to represent the fields. + + @note A new instance of the parser is required for each message. +*/ +template +class message_parser + : public basic_parser> +{ + using base_type = basic_parser>; + + using reader_type = typename Body::reader; + + message m_; + boost::optional r_; + +public: + /// The type of message returned by the parser + using value_type = message; + + /// The type of buffer sequence representing the body + using mutable_buffers_type = + typename reader_type::mutable_buffers_type; + + /// Constructor (default) + message_parser() = default; + + /// Copy constructor (disallowed) + message_parser(message_parser const&) = delete; + + /// Copy assignment (disallowed) + message_parser& operator=(message_parser const&) = delete; + + /** Move constructor. + + After the move, the only valid operation + on the moved-from object is destruction. + */ + message_parser(message_parser&& other); + + /** 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 http::header_parser or @ref message_parser. + */ +#if GENERATING_DOCS + template + explicit + msesage_parser(Args&&... args); +#else + template::type, + header_parser>::value && + ! std::is_same::type, message_parser>::value + >::type> + explicit + message_parser(Arg1&& arg1, ArgN&&... argn); +#endif + + /** Construct a message parser from a @ref header_parser. + + @param parser The header parser to construct from. + + @param args Optional arguments forwarded to the message + constructor. + */ + template + explicit + message_parser(header_parser< + isRequest, Fields>&& parser, Args&&... args); + + /** Returns the parsed message. + + Depending on the progress of the parser, portions + of this object may be incomplete. + */ + value_type const& + get() const + { + return m_; + } + + /** Returns the parsed message. + + Depending on the progress of the parser, portions + 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 progress of the parser, portions + 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_); + } + +private: + friend class basic_parser< + isRequest, Body::reader::is_direct, + message_parser>; + + void + on_request( + boost::string_ref const& method, + boost::string_ref const& path, + int version, error_code&) + { + m_.url = std::string{ + path.data(), path.size()}; + m_.method = std::string{ + method.data(), method.size()}; + m_.version = version; + } + + void + on_response(int status, + boost::string_ref const& reason, + int version, error_code&) + { + m_.status = status; + m_.reason = std::string{ + reason.data(), reason.size()}; + m_.version = version; + } + + void + on_field(boost::string_ref const& name, + boost::string_ref const& value, + error_code&) + { + m_.fields.insert(name, value); + } + + void + on_header(error_code& ec) + { + } + + void + on_body() + { + r_.emplace(m_); + r_->init(); + } + + void + on_body(std::uint64_t content_length) + { + r_.emplace(m_); + r_->init(content_length); + } + + void + on_body(error_code& ec) + { + r_.emplace(m_); + r_->init(ec); + if(ec) + return; + } + + void + on_body(std::uint64_t content_length, + error_code& ec) + { + r_.emplace(m_); + r_->init(content_length, ec); + if(ec) + return; + } + + void + on_data(boost::string_ref const& s, + error_code& ec) + { + static_assert(! Body::reader::is_direct, ""); + r_->write(s, ec); + } + + mutable_buffers_type + on_prepare(std::size_t n) + { + return r_->prepare(n); + } + + void + on_commit(std::size_t n) + { + r_->commit(n); + } + + void + on_chunk(std::uint64_t, + boost::string_ref const&, + error_code&) + { + } + + void + on_complete(error_code& ec) + { + if(r_) + do_on_complete(ec, + std::integral_constant{}); + } + + void + do_on_complete( + error_code& ec, std::true_type) + { + r_->finish(); + } + + void + do_on_complete( + error_code& ec, std::false_type) + { + r_->finish(ec); + if(ec) + return; + } +}; + +} // http +} // beast + +#include + +#endif 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_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..e0823da57c 100644 --- a/include/beast/http/read.hpp +++ b/include/beast/http/read.hpp @@ -11,135 +11,298 @@ #include #include #include +#include #include namespace beast { namespace http { -/** Read a HTTP/1 header from a stream. +/** Read some HTTP/1 message data from a stream. - 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 synchronously advances the state of the + parser using the provided dynamic buffer and reading + from the input stream as needed. The call will block + until one of the following conditions is true: + + @li When expecting a message header, and the complete + header is received. - @li An entire header is read in. + @li When expecting a chunk header, and the complete + chunk header is received. + + @li When expecting body octets, one or more body octets + are received. @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 - fields being parsed. This additional data is stored in the - stream buffer, which may be used in subsequent calls. + read additional octets that lie past the end of the object + being parsed. This additional data is stored in the dynamic + 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. - 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 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 + dynamic buffer's input sequence will be given to the parser + first. + + @param parser The parser to use. + + @return The number of bytes processed from the dynamic + buffer. The caller should remove these bytes by calling + `consume` on the dynamic buffer. + + @throws system_error Thrown on failure. +*/ +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, bool isDirect, class Derived> +std::size_t +read_some( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser); + +/** Read some HTTP/1 message data from a stream. + + This function synchronously advances the state of the + parser using the provided dynamic buffer and reading + from the input stream as needed. The call will block + until one of the following conditions is true: + + @li When expecting a message header, and the complete + header is received. + + @li When expecting a chunk header, and the complete + chunk header is received. + + @li When expecting body octets, one or more body octets + are received. + + @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 dynamic + 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. + The type must support the @b SyncReadStream concept. - @param dynabuf A @b `DynamicBuffer` holding additional bytes + @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 + 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. + + @return The number of bytes processed from the dynamic + buffer. The caller should remove these bytes by calling + `consume` on the dynamic buffer. +*/ +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, bool isDirect, class Derived> +std::size_t +read_some( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + error_code& ec); + +/** Start an asynchronous operation to read some HTTP/1 message data from a stream. + + This function asynchronously advances the state of the + parser using the provided dynamic buffer and reading from + the input stream as needed. The function call always + returns immediately. The asynchronous operation will + continue until one of the following conditions is true: + + @li When expecting a message header, and the complete + header is received. + + @li When expecting a chunk header, and the complete + chunk header is received. + + @li When expecting body octets, one or more body octets + are received. + + @li An error occurs in the stream or parser. + + 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 object being parsed. This additional data is stored + in the stream buffer, which may be used in subsequent calls. + + The completion handler will be called with the number of bytes + processed from the dynamic buffer. The caller should remove + these bytes by calling `consume` on the dynamic buffer. + + @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 + dynamic buffer's input sequence will be given to the parser + first. + + @param parser The parser to use. + + @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 + std::size_t bytes_used // the number of bytes to consume + ); @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, + bool isRequest, bool isDirect, class Derived, + class ReadHandler> +#if GENERATING_DOCS +void_or_deduced +#else +typename async_completion< + ReadHandler, void(error_code, std::size_t)>::result_type +#endif +async_read_some( + AsyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + ReadHandler&& handler); + +//------------------------------------------------------------------------------ + +/** Read an HTTP/1 message 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 is true: + + @li The parser indicates no more additional data is needed. + + @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 dynamic + 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 + dynamic buffer's input sequence will be given to the parser + first. + + @param parser The parser to use. @throws system_error Thrown on failure. */ -template +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, bool isDirect, class Derived> void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - header& msg); +read( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser); -/** Read a HTTP/1 header from a stream. +/** Read an HTTP/1 message from a stream. - 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 synchronously reads from a stream and passes + data to the specified parser. The call will block until one + of the following conditions is true: - @li An entire header is read in. + @li The parser indicates that no more data is needed. @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 - 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. + read additional octets that lie past the end of the object + being parsed. This additional data is stored in the dynamic + 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. + The type must support the @b SyncReadStream concept. - @param dynabuf A @b `DynamicBuffer` holding additional bytes + @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 + 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, bool isDirect, class Derived> void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - header& msg, - error_code& ec); +read( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + error_code& ec); -/** Read a HTTP/1 header asynchronously from a stream. +/** Start an asynchronous operation to read an HTTP/1 message from a stream. - 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 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 An entire header is read in. + @li The parser indicates that no more data is needed. @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 + 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. + 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. + @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 + @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 + 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. - @param handler The handler to be called when the operation + @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( @@ -150,20 +313,24 @@ 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 +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, bool isDirect, class Derived, + class ReadHandler> +#if BEAST_DOXYGEN void_or_deduced #else typename async_completion< ReadHandler, void(error_code)>::result_type #endif -async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf, - header& msg, - ReadHandler&& handler); +async_read( + AsyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + ReadHandler&& handler); -/** Read a HTTP/1 message from a stream. +/** Read an 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 @@ -176,7 +343,7 @@ async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf, 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 + being parsed. This additional data is stored in the dynamic buffer, which may be used in subsequent calls. @param stream The stream from which the data is to be read. @@ -185,7 +352,7 @@ async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf, @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 + dynamic buffer's input sequence will be given to the parser first. @param msg An object used to store the message. Any @@ -194,10 +361,14 @@ async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf, @throws system_error Thrown on failure. */ -template void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, +read( + SyncReadStream& stream, + DynamicBuffer& dynabuf, message& msg); /** Read a HTTP/1 message from a stream. @@ -213,7 +384,7 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf, 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 + being parsed. This additional data is stored in the dynamic buffer, which may be used in subsequent calls. @param stream The stream from which the data is to be read. @@ -222,7 +393,7 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf, @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 + dynamic buffer's input sequence will be given to the parser first. @param msg An object used to store the message. Any @@ -231,12 +402,16 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf, @param ec Set to the error, if any occurred. */ -template void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, +read( + SyncReadStream& stream, + DynamicBuffer& dynabuf, message& msg, - error_code& ec); + error_code& ec); /** Read a HTTP/1 message asynchronously from a stream. @@ -255,7 +430,7 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf, 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 - in the stream buffer, which may be used in subsequent calls. + in the dynamic 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. @@ -263,7 +438,7 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf, @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 + dynamic buffer's input sequence will be given to the parser first. @param msg An object used to store the header. Any contents @@ -282,22 +457,27 @@ 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 + class ReadHandler> +#if BEAST_DOXYGEN void_or_deduced #else typename async_completion< ReadHandler, void(error_code)>::result_type #endif -async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf, +async_read( + AsyncReadStream& stream, + DynamicBuffer& dynabuf, message& msg, - ReadHandler&& handler); + ReadHandler&& handler); } // http } // beast +#include #include #endif diff --git a/include/beast/http/rfc7230.hpp b/include/beast/http/rfc7230.hpp index 1f73194db1..aa30312096 100644 --- a/include/beast/http/rfc7230.hpp +++ b/include/beast/http/rfc7230.hpp @@ -10,6 +10,7 @@ #include #include +#include namespace beast { namespace http { @@ -61,7 +62,7 @@ class param_list std::pair; /// A constant iterator to the list -#if GENERATING_DOCS +#if BEAST_DOXYGEN using const_iterator = implementation_defined; #else class const_iterator; @@ -150,7 +151,7 @@ class ext_list 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; @@ -238,7 +239,7 @@ class token_list using value_type = boost::string_ref; /// A constant iterator to the list -#if GENERATING_DOCS +#if BEAST_DOXYGEN using const_iterator = implementation_defined; #else class const_iterator; @@ -276,6 +277,46 @@ class token_list 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/string_body.hpp b/include/beast/http/string_body.hpp index 707709c113..3427486444 100644 --- a/include/beast/http/string_body.hpp +++ b/include/beast/http/string_body.hpp @@ -28,35 +28,64 @@ struct string_body /// The type of the `message::body` member using value_type = std::string; -#if GENERATING_DOCS +#if BEAST_DOXYGEN private: #endif class reader { - value_type& s_; + value_type& body_; + std::size_t len_ = 0; public: + static bool constexpr is_direct = true; + + using mutable_buffers_type = + boost::asio::mutable_buffers_1; + template explicit reader(message& m) noexcept - : s_(m.body) + string_body, Fields>& m) + : body_(m.body) + { + } + + void + init() + { + } + + void + init(std::uint64_t content_length) + { + if(content_length > + (std::numeric_limits::max)()) + throw std::length_error{ + "Content-Length overflow"}; + body_.reserve(static_cast< + std::size_t>(content_length)); + } + + mutable_buffers_type + prepare(std::size_t n) { + body_.resize(len_ + n); + return {&body_[len_], n}; } void - init(error_code&) noexcept + commit(std::size_t n) { + if(body_.size() > len_ + n) + body_.resize(len_ + n); + len_ = body_.size(); } void - write(void const* data, - std::size_t size, error_code&) noexcept + finish() { - auto const n = s_.size(); - s_.resize(n + size); - std::memcpy(&s_[n], data, size); + body_.resize(len_); } }; diff --git a/include/beast/http/write.hpp b/include/beast/http/write.hpp index b190e2e627..563a86903c 100644 --- a/include/beast/http/write.hpp +++ b/include/beast/http/write.hpp @@ -121,7 +121,7 @@ write(SyncWriteStream& stream, template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else typename async_completion< @@ -240,7 +240,7 @@ write(SyncWriteStream& stream, template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else typename async_completion< diff --git a/include/beast/version.hpp b/include/beast/version.hpp index 2ba20a08a2..13782f4324 100644 --- a/include/beast/version.hpp +++ b/include/beast/version.hpp @@ -18,6 +18,6 @@ // #define BEAST_VERSION 100000 -#define BEAST_VERSION_STRING "1.0.0-b34" +#define BEAST_VERSION_STRING "1.0.0-b36" #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/frame.hpp b/include/beast/websocket/detail/frame.hpp index c3def4f3af..2a0cb628ef 100644 --- a/include/beast/websocket/detail/frame.hpp +++ b/include/beast/websocket/detail/frame.hpp @@ -67,33 +67,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 +173,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 +199,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/stream_base.hpp b/include/beast/websocket/detail/stream_base.hpp index 6cd6731fba..66ede62ec7 100644 --- a/include/beast/websocket/detail/stream_base.hpp +++ b/include/beast/websocket/detail/stream_base.hpp @@ -11,13 +11,11 @@ #include #include #include -#include #include #include #include #include #include -#include #include #include #include @@ -51,7 +49,6 @@ struct stream_base 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 @@ -154,16 +151,12 @@ struct stream_base // Offer for clients, negotiated result for servers pmd_offer pmd_config_; + stream_base() = default; 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); @@ -175,12 +168,12 @@ struct stream_base template std::size_t read_fh1(detail::frame_header& fh, - DynamicBuffer& db, close_code::value& code); + DynamicBuffer& db, close_code& code); template void read_fh2(detail::frame_header& fh, - DynamicBuffer& db, close_code::value& code); + DynamicBuffer& db, close_code& code); // Called before receiving the first frame of each message template @@ -264,13 +257,13 @@ template std::size_t stream_base:: read_fh1(detail::frame_header& fh, - DynamicBuffer& db, close_code::value& code) + DynamicBuffer& db, close_code& code) { using boost::asio::buffer; using boost::asio::buffer_copy; using boost::asio::buffer_size; auto const err = - [&](close_code::value cv) + [&](close_code cv) { code = cv; return 0; @@ -372,7 +365,7 @@ template void stream_base:: read_fh2(detail::frame_header& fh, - DynamicBuffer& db, close_code::value& code) + DynamicBuffer& db, close_code& code) { using boost::asio::buffer; using boost::asio::buffer_copy; diff --git a/include/beast/websocket/detail/type_traits.hpp b/include/beast/websocket/detail/type_traits.hpp new file mode 100644 index 0000000000..58be3caf18 --- /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_call_possible::type; + +template +using is_ResponseDecorator = + typename beast::detail::is_call_possible::type; + +} // detail +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/impl/accept.ipp b/include/beast/websocket/impl/accept.ipp index 877be120c1..2543d0f7ce 100644 --- a/include/beast/websocket/impl/accept.ipp +++ b/include/beast/websocket/impl/accept.ipp @@ -8,8 +8,9 @@ #ifndef BEAST_WEBSOCKET_IMPL_ACCEPT_IPP #define BEAST_WEBSOCKET_IMPL_ACCEPT_IPP +#include #include -#include +#include #include #include #include @@ -35,23 +36,37 @@ class stream::response_op { bool cont; stream& ws; - http::response res; - error_code final_ec; + http::response_header res; int state = 0; - template + template data(Handler&, stream& ws_, - http::request const& req, - bool cont_) + http::header 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 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)); } }; @@ -126,7 +141,8 @@ operator()(error_code ec, bool again) // sent response case 1: d.state = 99; - ec = d.final_ec; + if(d.res.status != 101) + ec = error::handshake_failed; if(! ec) { pmd_read( @@ -144,26 +160,36 @@ 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; + Decorator decorator; + http::header_parser p; int state = 0; + data(Handler& handler, stream& ws_, + Decorator const& decorator_) + : cont(beast_asio_helpers::is_continuation(handler)) + , ws(ws_) + , decorator(decorator_) + { + } + template data(Handler& handler, stream& ws_, - Buffers const& buffers) - : cont(beast_asio_helpers:: - is_continuation(handler)) + Buffers const& buffers, + Decorator const& decorator_) + : cont(beast_asio_helpers::is_continuation(handler)) , 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)); @@ -185,13 +211,8 @@ public: (*this)(error_code{}, 0, false); } - void operator()(error_code const& ec) - { - (*this)(ec, 0); - } - - void operator()(error_code const& ec, - std::size_t bytes_transferred, bool again = true); + void operator()(error_code ec, + std::size_t bytes_used, bool again = true); friend void* asio_handler_allocate( @@ -225,132 +246,147 @@ public: }; template -template +template void -stream::accept_op:: -operator()(error_code const& ec, - std::size_t bytes_transferred, bool again) +stream::accept_op:: +operator()(error_code ec, + std::size_t bytes_used, bool again) { - beast::detail::ignore_unused(bytes_transferred); auto& d = *d_; d.cont = d.cont || again; - while(! ec && d.state != 99) + if(ec) + goto upcall; + switch(d.state) { - 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; + case 0: + // read message + d.state = 1; + http::async_read_some(d.ws.next_layer(), + d.ws.stream_.buffer(), d.p, + std::move(*this)); + return; - // 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; - } - } + case 1: + { + BOOST_ASSERT(d.p.got_header()); + d.ws.stream_.buffer().consume(bytes_used); + // 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; + } } +upcall: d_.invoke(ec); } +//------------------------------------------------------------------------------ + template -template -typename async_completion< - AcceptHandler, void(error_code)>::result_type +void stream:: -async_accept(AcceptHandler&& handler) +accept() { - static_assert(is_AsyncStream::value, - "AsyncStream requirements requirements not met"); - return async_accept(boost::asio::null_buffers{}, - std::forward(handler)); + static_assert(is_SyncStream::value, + "SyncStream requirements not met"); + error_code ec; + accept(ec); + if(ec) + throw system_error{ec}; } template -template -typename async_completion< - AcceptHandler, void(error_code)>::result_type +template +void stream:: -async_accept(ConstBufferSequence const& bs, AcceptHandler&& handler) +accept_ex(ResponseDecorator const& decorator) { - 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(); + static_assert(is_SyncStream::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) + throw system_error{ec}; } template -template -typename async_completion< - AcceptHandler, void(error_code)>::result_type +void stream:: -async_accept(http::request const& req, - AcceptHandler&& handler) +accept(error_code& ec) { - static_assert(is_AsyncStream::value, - "AsyncStream requirements requirements not met"); - beast::async_completion< - AcceptHandler, void(error_code) - > completion{handler}; + static_assert(is_SyncStream::value, + "SyncStream requirements not met"); reset(); - response_op{ - completion.handler, *this, req, - beast_asio_helpers:: - is_continuation(completion.handler)}; - return completion.result.get(); + do_accept(&default_decorate_res, ec); } template +template void stream:: -accept() +accept_ex(ResponseDecorator const& decorator, error_code& ec) { static_assert(is_SyncStream::value, "SyncStream requirements not met"); - error_code ec; - accept(boost::asio::null_buffers{}, ec); - if(ec) - throw system_error{ec}; + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + reset(); + do_accept(decorator, ec); } template +template void stream:: -accept(error_code& ec) +accept(ConstBufferSequence const& buffers) { static_assert(is_SyncStream::value, "SyncStream requirements not met"); - accept(boost::asio::null_buffers{}, ec); + static_assert(is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + error_code ec; + accept(buffers, ec); + if(ec) + throw system_error{ec}; } template -template +template< + class ConstBufferSequence, class ResponseDecorator> void stream:: -accept(ConstBufferSequence const& buffers) +accept_ex(ConstBufferSequence const& buffers, + ResponseDecorator const &decorator) { static_assert(is_SyncStream::value, "SyncStream requirements not met"); static_assert(is_ConstBufferSequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); error_code ec; - accept(buffers, ec); + accept_ex(buffers, decorator, ec); if(ec) throw system_error{ec}; } @@ -363,63 +399,380 @@ accept(ConstBufferSequence const& buffers, error_code& ec) { static_assert(is_SyncStream::value, "SyncStream requirements not met"); - static_assert(beast::is_ConstBufferSequence< + static_assert(is_ConstBufferSequence< 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(&default_decorate_res, ec); +} + +template +template< + class ConstBufferSequence, class ResponseDecorator> +void +stream:: +accept_ex(ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, error_code& ec) +{ + static_assert(is_SyncStream::value, + "SyncStream requirements not met"); + static_assert(is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + static_assert(is_ConstBufferSequence< + 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)); - http::request m; - http::read(next_layer(), stream_.buffer(), m, ec); + do_accept(decorator, ec); +} + +template +template +void +stream:: +accept(http::header const& req) +{ + static_assert(is_SyncStream::value, + "SyncStream requirements not met"); + error_code ec; + accept(req, ec); if(ec) - return; - accept(m, ec); + throw system_error{ec}; } template -template +template void stream:: -accept(http::request const& request) +accept_ex(http::header const& req, + ResponseDecorator const& decorator) { static_assert(is_SyncStream::value, "SyncStream requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); error_code ec; - accept(request, ec); + accept_ex(req, decorator, ec); if(ec) throw system_error{ec}; } template -template +template void stream:: -accept(http::request const& req, +accept(http::header const& req, error_code& ec) { static_assert(is_SyncStream::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_SyncStream::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_SyncStream::value, + "SyncStream requirements not met"); + static_assert(is_ConstBufferSequence< + 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); + throw system_error{ec}; +} + +template +template +void +stream:: +accept_ex(http::header const& req, + ConstBufferSequence const& buffers, + ResponseDecorator const& decorator) +{ + static_assert(is_SyncStream::value, + "SyncStream requirements not met"); + static_assert(is_ConstBufferSequence< + 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) + throw system_error{ec}; +} + +template +template +void +stream:: +accept(http::header const& req, + ConstBufferSequence const& buffers, error_code& ec) +{ + static_assert(is_SyncStream::value, + "SyncStream requirements not met"); + static_assert(is_ConstBufferSequence< + 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_SyncStream::value, + "SyncStream requirements not met"); + static_assert(is_ConstBufferSequence< + 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 +typename async_completion::result_type +stream:: +async_accept(AcceptHandler&& handler) +{ + static_assert(is_AsyncStream::value, + "AsyncStream requirements requirements not met"); + beast::async_completion completion{handler}; + reset(); + accept_op{completion.handler, + *this, &default_decorate_res}; + return completion.result.get(); +} + +template +template +typename async_completion::result_type +stream:: +async_accept_ex(ResponseDecorator const& decorator, + AcceptHandler&& handler) +{ + static_assert(is_AsyncStream::value, + "AsyncStream requirements requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + beast::async_completion completion{handler}; + reset(); + accept_op{ + completion.handler, *this, decorator}; + return completion.result.get(); +} + +template +template +typename async_completion::result_type +stream:: +async_accept(ConstBufferSequence const& buffers, + AcceptHandler&& handler) +{ + static_assert(is_AsyncStream::value, + "AsyncStream requirements requirements not met"); + static_assert(is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + beast::async_completion completion{handler}; + reset(); + accept_op{completion.handler, + *this, buffers, &default_decorate_res}; + return completion.result.get(); +} + +template +template +typename async_completion::result_type +stream:: +async_accept_ex(ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, + AcceptHandler&& handler) +{ + static_assert(is_AsyncStream::value, + "AsyncStream requirements requirements not met"); + static_assert(is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + beast::async_completion completion{handler}; + reset(); + accept_op{ + completion.handler, *this, buffers, decorator}; + return completion.result.get(); +} + +template +template +typename async_completion::result_type +stream:: +async_accept(http::header const& req, + AcceptHandler&& handler) +{ + static_assert(is_AsyncStream::value, + "AsyncStream requirements requirements not met"); + beast::async_completion completion{handler}; + reset(); + response_op{ + completion.handler, *this, req, + &default_decorate_res, beast_asio_helpers:: + is_continuation(completion.handler)}; + return completion.result.get(); +} + +template +template +typename async_completion::result_type +stream:: +async_accept_ex(http::header const& req, + ResponseDecorator const& decorator, AcceptHandler&& handler) +{ + static_assert(is_AsyncStream::value, + "AsyncStream requirements requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + beast::async_completion completion{handler}; + reset(); + response_op{ + completion.handler, *this, req, decorator, + beast_asio_helpers::is_continuation( + completion.handler)}; + return completion.result.get(); +} + +template +template +typename async_completion::result_type +stream:: +async_accept(http::header const& req, + ConstBufferSequence const& buffers, + AcceptHandler&& handler) +{ + static_assert(is_AsyncStream::value, + "AsyncStream requirements requirements not met"); + static_assert(is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + beast::async_completion completion{handler}; + reset(); + response_op{ + completion.handler, *this, req, buffers, + &default_decorate_res, beast_asio_helpers:: + is_continuation(completion.handler)}; + return completion.result.get(); +} + +template +template +typename async_completion::result_type +stream:: +async_accept_ex(http::header const& req, + ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, + AcceptHandler&& handler) +{ + static_assert(is_AsyncStream::value, + "AsyncStream requirements requirements not met"); + static_assert(is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + beast::async_completion completion{handler}; + reset(); + response_op{ + completion.handler, *this, req, buffers, decorator, + beast_asio_helpers::is_continuation( + completion.handler)}; + return completion.result.get(); +} + } // websocket } // beast diff --git a/include/beast/websocket/impl/handshake.ipp b/include/beast/websocket/impl/handshake.ipp index 11ea56181a..b7ea7a188d 100644 --- a/include/beast/websocket/impl/handshake.ipp +++ b/include/beast/websocket/impl/handshake.ipp @@ -8,9 +8,10 @@ #ifndef BEAST_WEBSOCKET_IMPL_HANDSHAKE_IPP #define BEAST_WEBSOCKET_IMPL_HANDSHAKE_IPP -#include +#include #include #include +#include #include #include #include @@ -33,18 +34,24 @@ class stream::handshake_op { bool cont; stream& ws; + response_type* res_p; std::string key; - http::request req; - http::response resp; + request_type req; + response_type res; int state = 0; + template data(Handler& handler, stream& ws_, - boost::string_ref const& host, - boost::string_ref const& resource) + response_type* res_p_, + boost::string_ref const& host, + boost::string_ref const& resource, + Decorator const& decorator) : cont(beast_asio_helpers:: is_continuation(handler)) , ws(ws_) - , req(ws.build_request(host, resource, key)) + , res_p(res_p_) + , req(ws.build_request(key, + host, resource, decorator)) { ws.reset(); } @@ -117,10 +124,14 @@ 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); 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,38 +140,108 @@ 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 +typename async_completion::result_type stream:: async_handshake(boost::string_ref const& host, - boost::string_ref const& resource, HandshakeHandler&& handler) + boost::string_ref const& resource, + HandshakeHandler&& handler) { static_assert(is_AsyncStream::value, "AsyncStream requirements not met"); - beast::async_completion< - HandshakeHandler, void(error_code) - > completion{handler}; + beast::async_completion completion{handler}; handshake_op{ - completion.handler, *this, host, resource}; + completion.handler, *this, nullptr, + host, resource, &default_decorate_req}; + return completion.result.get(); +} + +template +template +typename async_completion::result_type +stream:: +async_handshake(response_type& res, + boost::string_ref const& host, + boost::string_ref const& resource, + HandshakeHandler&& handler) +{ + static_assert(is_AsyncStream::value, + "AsyncStream requirements not met"); + beast::async_completion completion{handler}; + handshake_op{ + completion.handler, *this, &res, + host, resource, &default_decorate_req}; + return completion.result.get(); +} + +template +template +typename async_completion::result_type +stream:: +async_handshake_ex(boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator, + HandshakeHandler&& handler) +{ + static_assert(is_AsyncStream::value, + "AsyncStream requirements not met"); + static_assert(detail::is_RequestDecorator< + RequestDecorator>::value, + "RequestDecorator requirements not met"); + beast::async_completion completion{handler}; + handshake_op{ + completion.handler, *this, nullptr, + host, resource, decorator}; + return completion.result.get(); +} + +template +template +typename async_completion::result_type +stream:: +async_handshake_ex(response_type& res, + boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator, + HandshakeHandler&& handler) +{ + static_assert(is_AsyncStream::value, + "AsyncStream requirements not met"); + static_assert(detail::is_RequestDecorator< + RequestDecorator>::value, + "RequestDecorator requirements not met"); + beast::async_completion completion{handler}; + handshake_op{ + completion.handler, *this, &res, + host, resource, decorator}; return completion.result.get(); } @@ -173,7 +254,8 @@ handshake(boost::string_ref const& host, static_assert(is_SyncStream::value, "SyncStream requirements not met"); error_code ec; - handshake(host, resource, ec); + do_handshake(nullptr, + host, resource, &default_decorate_req, ec); if(ec) throw system_error{ec}; } @@ -181,26 +263,100 @@ handshake(boost::string_ref const& host, template void stream:: -handshake(boost::string_ref const& host, - boost::string_ref const& resource, error_code& ec) +handshake(response_type& res, + boost::string_ref const& host, + boost::string_ref const& resource) { static_assert(is_SyncStream::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; + do_handshake(&res, + host, resource, &default_decorate_req, ec); if(ec) - return; - http::response res; - http::read(next_layer(), stream_.buffer(), res, ec); + throw system_error{ec}; +} + +template +template +void +stream:: +handshake_ex(boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator) +{ + static_assert(is_SyncStream::value, + "SyncStream requirements not met"); + static_assert(detail::is_RequestDecorator< + RequestDecorator>::value, + "RequestDecorator requirements not met"); + error_code ec; + do_handshake(nullptr, host, resource, decorator, ec); + if(ec) + throw system_error{ec}; +} + +template +template +void +stream:: +handshake_ex(response_type& res, + boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator) +{ + static_assert(is_SyncStream::value, + "SyncStream requirements not met"); + static_assert(detail::is_RequestDecorator< + RequestDecorator>::value, + "RequestDecorator requirements not met"); + error_code ec; + do_handshake(&res, host, resource, decorator, ec); if(ec) - return; - do_response(res, key, ec); + throw system_error{ec}; +} + +template +void +stream:: +handshake(boost::string_ref const& host, + boost::string_ref const& resource, error_code& ec) +{ + static_assert(is_SyncStream::value, + "SyncStream requirements not met"); + do_handshake(nullptr, + host, resource, &default_decorate_req, ec); +} + +template +void +stream:: +handshake(response_type& res, + boost::string_ref const& host, + boost::string_ref const& resource, + error_code& ec) +{ + static_assert(is_SyncStream::value, + "SyncStream requirements not met"); + do_handshake(res, + host, resource, &default_decorate_req, ec); +} + +template +template +void +stream:: +handshake_ex(boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator, + error_code& ec) +{ + static_assert(is_SyncStream::value, + "SyncStream requirements not met"); + static_assert(detail::is_RequestDecorator< + RequestDecorator>::value, + "RequestDecorator requirements not met"); + do_handshake(nullptr, + host, resource, decorator, ec); } //------------------------------------------------------------------------------ diff --git a/include/beast/websocket/impl/read.ipp b/include/beast/websocket/impl/read.ipp index 4defa6743e..acb940c9cb 100644 --- a/include/beast/websocket/impl/read.ipp +++ b/include/beast/websocket/impl/read.ipp @@ -173,7 +173,7 @@ operator()(error_code ec, if(! ec) { d.cont = d.cont || again; - close_code::value code = close_code::none; + close_code code = close_code::none; do { switch(d.state) @@ -727,7 +727,7 @@ read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec) 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 diff --git a/include/beast/websocket/impl/rfc6455.ipp b/include/beast/websocket/impl/rfc6455.ipp new file mode 100644 index 0000000000..684112bb1f --- /dev/null +++ b/include/beast/websocket/impl/rfc6455.ipp @@ -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) +// + +#ifndef BEAST_WEBSOCKET_IMPL_RFC6455_IPP +#define BEAST_WEBSOCKET_IMPL_RFC6455_IPP + +#include + +namespace beast { +namespace websocket { + +template +bool +is_upgrade(http::header const& req) +{ + if(req.version < 11) + return false; + if(req.method != "GET") + return false; + if(! http::is_upgrade(req)) + return false; + if(! http::token_list{req.fields["Upgrade"]}.exists("websocket")) + return false; + if(! req.fields.exists("Sec-WebSocket-Version")) + return false; + return true; +} + +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/impl/stream.ipp b/include/beast/websocket/impl/stream.ipp index fa932ffc7f..717e9aef68 100644 --- a/include/beast/websocket/impl/stream.ipp +++ b/include/beast/websocket/impl/stream.ipp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -83,17 +84,89 @@ reset() } template -http::request +template +void +stream:: +do_accept( + Decorator const& decorator, error_code& ec) +{ + http::header_parser p; + auto const bytes_used = http::read_some( + next_layer(), stream_.buffer(), p, ec); + if(ec) + return; + BOOST_ASSERT(p.got_header()); + stream_.buffer().consume(bytes_used); + do_accept(p.get(), decorator, ec); +} + +template +template +void stream:: -build_request(boost::string_ref const& host, - boost::string_ref const& resource, std::string& key) +do_accept(http::header const& req, + Decorator const& decorator, error_code& ec) { - http::request req; + auto const res = build_response(req, decorator); + http::write(stream_, res, 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); +} + +template +template +void +stream:: +do_handshake(response_type* res_p, + boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator, + error_code& ec) +{ + response_type res; + reset(); + std::string key; + { + auto const req = build_request( + key, host, resource, decorator); + pmd_read(pmd_config_, req.fields); + 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) + swap(res, *res_p); +} + +template +template +request_type +stream:: +build_request(std::string& key, + boost::string_ref const& host, + boost::string_ref const& resource, + Decorator const& decorator) +{ + request_type req; req.url = { resource.data(), resource.size() }; req.version = 11; req.method = "GET"; req.fields.insert("Host", host); req.fields.insert("Upgrade", "websocket"); + req.fields.insert("Connection", "upgrade"); key = detail::make_sec_ws_key(maskgen_); req.fields.insert("Sec-WebSocket-Key", key); req.fields.insert("Sec-WebSocket-Version", "13"); @@ -112,30 +185,42 @@ build_request(boost::string_ref const& host, detail::pmd_write( req.fields, config); } - d_(req); - http::prepare(req, http::connection::upgrade); + decorator(req); + if(! req.fields.exists("User-Agent")) + req.fields.insert("User-Agent", + std::string("Beast/") + BEAST_VERSION_STRING); return req; } template -template -http::response +template +response_type stream:: -build_response(http::request const& req) +build_response(request_type const& req, + Decorator const& decorator) { + auto const decorate = + [&decorator](response_type& res) + { + decorator(res); + if(! res.fields.exists("Server")) + res.fields.insert("Server", + std::string("Beast/") + + BEAST_VERSION_STRING); + }; auto err = [&](std::string const& text) { - http::response res; + response_type res; res.status = 400; res.reason = http::reason_string(res.status); res.version = req.version; res.body = text; - d_(res); prepare(res, (is_keep_alive(req) && keep_alive_) ? http::connection::keep_alive : http::connection::close); + decorate(res); return res; }; if(req.version < 11) @@ -157,20 +242,20 @@ build_response(http::request const& req) return err("Missing Sec-WebSocket-Version"); if(version != "13") { - http::response res; + response_type res; res.status = 426; res.reason = http::reason_string(res.status); 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); + decorate(res); return res; } } - http::response res; + response_type res; { detail::pmd_offer offer; detail::pmd_offer unused; @@ -182,23 +267,21 @@ build_response(http::request const& req) res.reason = http::reason_string(res.status); res.version = req.version; res.fields.insert("Upgrade", "websocket"); + res.fields.insert("Connection", "upgrade"); { auto const key = req.fields["Sec-WebSocket-Key"]; res.fields.insert("Sec-WebSocket-Accept", detail::make_sec_ws_accept(key)); } - 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, +do_response(http::response_header const& res, boost::string_ref const& key, error_code& ec) { // VFALCO Review these error codes diff --git a/include/beast/websocket/option.hpp b/include/beast/websocket/option.hpp index 005ae7b238..feba8912ee 100644 --- a/include/beast/websocket/option.hpp +++ b/include/beast/websocket/option.hpp @@ -10,7 +10,6 @@ #include #include -#include #include #include #include @@ -44,7 +43,7 @@ namespace websocket { stream.set_option(auto_fragment{true}); @endcode */ -#if GENERATING_DOCS +#if BEAST_DOXYGEN using auto_fragment = implementation_defined; #else struct auto_fragment @@ -59,56 +58,6 @@ struct auto_fragment }; #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 @@ -133,7 +82,7 @@ using decorate = detail::decorator_type; ws.set_option(keep_alive{8192}); @endcode */ -#if GENERATING_DOCS +#if BEAST_DOXYGEN using keep_alive = implementation_defined; #else struct keep_alive @@ -169,7 +118,7 @@ struct keep_alive ws.set_option(message_type{opcode::binary}); @endcode */ -#if GENERATING_DOCS +#if BEAST_DOXYGEN using message_type = implementation_defined; #else struct message_type @@ -270,7 +219,7 @@ struct permessage_deflate To remove the ping callback, construct the option with no parameters: `set_option(ping_callback{})` */ -#if GENERATING_DOCS +#if BEAST_DOXYGEN using ping_callback = implementation_defined; #else struct ping_callback @@ -312,7 +261,7 @@ struct ping_callback ws.set_option(read_buffer_size{16 * 1024}); @endcode */ -#if GENERATING_DOCS +#if BEAST_DOXYGEN using read_buffer_size = implementation_defined; #else struct read_buffer_size @@ -350,7 +299,7 @@ struct read_buffer_size ws.set_option(read_message_max{65536}); @endcode */ -#if GENERATING_DOCS +#if BEAST_DOXYGEN using read_message_max = implementation_defined; #else struct read_message_max @@ -393,7 +342,7 @@ struct read_message_max ws.set_option(write_buffer_size{8192}); @endcode */ -#if GENERATING_DOCS +#if BEAST_DOXYGEN using write_buffer_size = implementation_defined; #else struct write_buffer_size diff --git a/include/beast/websocket/rfc6455.hpp b/include/beast/websocket/rfc6455.hpp index 3acb11e53d..6813ed3f76 100644 --- a/include/beast/websocket/rfc6455.hpp +++ b/include/beast/websocket/rfc6455.hpp @@ -10,13 +10,34 @@ #include #include -#include +#include #include #include namespace beast { namespace websocket { +/** 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. + + @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); + /** WebSocket frame header opcodes. */ enum class opcode : std::uint8_t { @@ -45,48 +66,87 @@ enum class opcode : std::uint8_t @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 { - /// 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, + //---- + // + // The following are illegal on the wire + // + + /** 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 = 1005, // illegal on wire - abnormal = 1006, // illegal on wire - reserved2 = 1015, - last = 5000 // satisfy warnings + /** 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 +162,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,7 +175,7 @@ struct close_reason close_reason() = default; /// Construct from a code. - close_reason(close_code::value code_) + close_reason(std::uint16_t code_) : code(code_) { } @@ -130,7 +190,7 @@ struct close_reason /// Construct from a code and reason. template - close_reason(close_code::value code_, + close_reason(close_code code_, char const (&reason_)[N]) : code(code_) , reason(reason_) @@ -147,4 +207,6 @@ struct close_reason } // websocket } // beast +#include + #endif diff --git a/include/beast/websocket/stream.hpp b/include/beast/websocket/stream.hpp index 5f58042af4..66542ca681 100644 --- a/include/beast/websocket/stream.hpp +++ b/include/beast/websocket/stream.hpp @@ -13,18 +13,27 @@ #include #include #include -#include #include +#include #include #include #include #include #include #include +#include namespace beast { namespace websocket { +/// The type of object holding HTTP Upgrade requests +using request_type = http::request_header; + +/// The type of object holding HTTP Upgrade responses +using response_type = + //http::response_header; + http::response; + /** Information about a WebSocket frame. This information is provided to callers during frame @@ -59,7 +68,7 @@ struct frame_info you would write: @code - websocket::stream ws(io_service); + websocket::stream ws{io_service}; @endcode Alternatively, you can write: @code @@ -78,7 +87,6 @@ struct frame_info @par Concepts @b `AsyncStream`, - @b `Decorator`, @b `DynamicBuffer`, @b `SyncStream` */ @@ -96,7 +104,7 @@ class stream : public detail::stream_base /// The type of the lowest layer. using lowest_layer_type = - #if GENERATING_DOCS + #if BEAST_DOXYGEN implementation_defined; #else typename beast::detail::get_lowest_layer< @@ -152,7 +160,7 @@ class stream : public detail::stream_base @param args One or more stream options to set. */ -#if GENERATING_DOCS +#if BEAST_DOXYGEN template void set_option(Args&&... args) @@ -174,33 +182,6 @@ class stream : public detail::stream_base 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) @@ -340,25 +321,25 @@ class stream : public detail::stream_base /** 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 HTTP request finishes receiving, and the 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. + 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 +349,63 @@ class stream : public detail::stream_base /** 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 HTTP request finishes receiving, and the 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. + 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 HTTP request finishes receiving, and the 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, + 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,75 +413,664 @@ class stream : public detail::stream_base 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 HTTP request finishes receiving, and the 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, + 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 HTTP request finishes receiving, and the 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, + 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 + void + 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 HTTP request finishes receiving, and the 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, + 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 + void + 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 HTTP request finishes receiving, and the 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, + 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 + void + 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 HTTP request finishes receiving, and the 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, + 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 + void + 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 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, + 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 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, + 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 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, + 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 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, + 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 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, + 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 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, + 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 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, + 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 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, + 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 HTTP request finishes receiving, and the 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_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, + 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 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 + typename async_completion< + AcceptHandler, void(error_code)>::result_type +#endif + async_accept(AcceptHandler&& handler); - 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. + /** Start reading and responding to a WebSocket HTTP Upgrade request. - 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. + 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: - @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: + @li The HTTP request finishes receiving, and the 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_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 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& 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 #endif - async_accept(AcceptHandler&& handler); + async_accept_ex(ResponseDecorator const& decorator, + AcceptHandler&& handler); - /** Read and respond to a WebSocket HTTP Upgrade request. + /** Start reading and responding 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 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 HTTP request finishes receiving, and the 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. + 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 buffers Caller provided data that has already been received on the stream. This may be used for implementations @@ -471,34 +1079,56 @@ class stream : public detail::stream_base then to received WebSocket frames. The implementation will copy the caller provided data before the function returns. - @throws system_error Thrown on failure. + @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 - void - accept(ConstBufferSequence const& buffers); + template +#if BEAST_DOXYGEN + void_or_deduced +#else + typename async_completion::result_type +#endif + async_accept(ConstBufferSequence const& buffers, + AcceptHandler&& handler); - /** Read and respond to a WebSocket HTTP Upgrade request. + /** Start reading and responding 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 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 HTTP request finishes receiving, and the 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. + 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 buffers Caller provided data that has already been received on the stream. This may be used for implementations @@ -507,191 +1137,304 @@ class stream : public detail::stream_base 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. + @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 - void - accept(ConstBufferSequence const& buffers, error_code& ec); + template +#if BEAST_DOXYGEN + void_or_deduced +#else + typename async_completion::result_type +#endif + async_accept_ex(ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, + AcceptHandler&& handler); - /** Start reading and responding to a WebSocket HTTP Upgrade request. + /** Start responding to a WebSocket HTTP Upgrade request. - This function is used to asynchronously read a 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: + 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 A HTTP request finishes receiving, and a HTTP response finishes - sending. + @li The 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_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 + 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, 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 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 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: + @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 async_completion::result_type #endif - async_accept(ConstBufferSequence const& buffers, + async_accept(http::header const& req, AcceptHandler&& handler); - /** Respond to a WebSocket HTTP Upgrade request + /** Start 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 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 A HTTP response finishes sending. + @li The 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. + 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 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. + @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. + @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`. */ - // VFALCO TODO This should also take a DynamicBuffer with any leftover bytes. - template - void - accept(http::request const& request); + template +#if BEAST_DOXYGEN + void_or_deduced +#else + typename async_completion::result_type +#endif + async_accept_ex(http::header const& req, + ResponseDecorator const& decorator, + AcceptHandler&& handler); - /** Respond to a WebSocket HTTP Upgrade request + /** Start 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 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 A HTTP response finishes sending. + @li The 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. + 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 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. + @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. + @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 - void - accept(http::request const& request, - error_code& ec); + template +#if BEAST_DOXYGEN + void_or_deduced +#else + typename async_completion::result_type +#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 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: + 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 A HTTP response finishes sending. + @li The 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. + 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 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. + @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: + @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& 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 async_completion::result_type #endif - async_accept(http::request const& request, - AcceptHandler&& handler); + async_accept_ex(http::header const& req, + ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, + AcceptHandler&& handler); - /** 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 + @li A HTTP request finishes sending and an HTTP response finishes receiving. @li An error occurs on the stream @@ -709,11 +1452,14 @@ class stream : public detail::stream_base @param resource The requesting URI, which may not be empty, required by the HTTP protocol. + @return The HTTP Upgrade response returned by the remote + endpoint. + @throws system_error Thrown on failure. @par Example @code - websocket::stream ws(io_service); + websocket::stream ws{io_service}; ... try { @@ -729,13 +1475,87 @@ class stream : public detail::stream_base handshake(boost::string_ref const& host, boost::string_ref const& resource); - /** Send a HTTP WebSocket Upgrade request and receive the response. + void + handshake(response_type& res, + boost::string_ref const& host, + boost::string_ref const& resource); + + /** 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 an HTTP response finishes + receiving. + + @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 resource The requesting URI, 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 + + @return The HTTP Upgrade response returned by the remote + endpoint. + + @throws system_error Thrown on failure. + + @par Example + @code + websocket::stream ws{io_service}; + ... + try + { + ws.upgrade("localhost", "/", + [](request_type& req) + { + req.fields.insert("User-Agent", "Beast"); + }); + } + catch(...) + { + // An error occurred. + } + @endcode + */ + template + void + handshake_ex(boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator); + + template + void + handshake_ex(response_type& res, + boost::string_ref const& host, + boost::string_ref const& resource, + 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 A HTTP request finishes sending and a HTTP response finishes + @li A HTTP request finishes sending and an HTTP response finishes receiving. @li An error occurs on the stream @@ -755,9 +1575,12 @@ class stream : public detail::stream_base @param ec Set to indicate what error occurred, if any. + @return 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); + websocket::stream ws{io_service}; ... error_code ec; ws.upgrade(host, resource, ec); @@ -771,6 +1594,82 @@ class stream : public detail::stream_base handshake(boost::string_ref const& host, boost::string_ref const& resource, error_code& ec); + void + handshake(response_type& res, + boost::string_ref const& host, + boost::string_ref const& resource, + 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 A HTTP request finishes sending and an HTTP response finishes + receiving. + + @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 resource The requesting URI, 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. + + @return 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; + ws.upgrade("localhost", "/", + [](request_type& req) + { + req.fields.insert("User-Agent", "Beast"); + }, + ec); + if(ec) + { + // An error occurred. + } + @endcode + */ + template + void + handshake_ex(boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator, + error_code& ec); + + template + void + handshake_ex(response_type& res, + boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator, + error_code& ec); + /** Start an asynchronous operation to send an upgrade request and receive the response. This function is used to asynchronously send the HTTP WebSocket @@ -779,7 +1678,7 @@ class stream : public detail::stream_base operation will continue until one of the following conditions is true: - @li A HTTP request finishes sending and a HTTP response finishes + @li A HTTP request finishes sending and an HTTP response finishes receiving. @li An error occurs on the stream. @@ -801,11 +1700,14 @@ class stream : public detail::stream_base required by the HTTP protocol. Copies 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 + response_type const& res // The HTTP Upgrade response returned + // by the remote endpoint. Undefined if + // `ec is set. ); @endcode Regardless of whether the asynchronous operation completes immediately or not, the handler will not be invoked from within @@ -813,14 +1715,105 @@ class stream : public detail::stream_base 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 + typename async_completion::result_type #endif async_handshake(boost::string_ref const& host, - boost::string_ref const& resource, HandshakeHandler&& h); + boost::string_ref const& resource, + HandshakeHandler&& handler); + + template +#if BEAST_DOXYGEN + void_or_deduced +#else + typename async_completion::result_type +#endif + async_handshake(response_type& res, + boost::string_ref const& host, + boost::string_ref const& resource, + 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 A HTTP request finishes sending and an HTTP response finishes + receiving. + + @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 resource The requesting URI, which may not be empty, + required by the HTTP protocol. Copies 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 + response_type const& res // The HTTP Upgrade response returned + // by the remote endpoint. Undefined if + // `ec is set. + ); @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 + typename async_completion::result_type +#endif + async_handshake_ex(boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator, + HandshakeHandler&& handler); + + template +#if BEAST_DOXYGEN + void_or_deduced +#else + typename async_completion::result_type +#endif + async_handshake_ex(response_type& res, + boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator, + HandshakeHandler&& handler); /** Send a WebSocket close frame. @@ -915,7 +1908,7 @@ class stream : public detail::stream_base 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,7 +1917,7 @@ class stream : public detail::stream_base manner equivalent to using `boost::asio::io_service::post`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else typename async_completion< @@ -997,7 +1990,7 @@ class stream : public detail::stream_base 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,7 +1999,7 @@ class stream : public detail::stream_base manner equivalent to using `boost::asio::io_service::post`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else typename async_completion< @@ -1094,7 +2087,7 @@ class stream : public detail::stream_base 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,7 +2096,7 @@ class stream : public detail::stream_base manner equivalent to using `boost::asio::io_service::post`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else typename async_completion< @@ -1242,7 +2235,7 @@ class stream : public detail::stream_base 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,7 +2244,7 @@ class stream : public detail::stream_base manner equivalent to using `boost::asio::io_service::post`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else typename async_completion< @@ -1400,7 +2393,7 @@ class stream : public detail::stream_base 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 @@ -1409,7 +2402,7 @@ class stream : public detail::stream_base manner equivalent to using boost::asio::io_service::post(). */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else typename async_completion< @@ -1526,7 +2519,7 @@ class stream : public detail::stream_base 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,7 +2528,7 @@ class stream : public detail::stream_base manner equivalent to using `boost::asio::io_service::post`. */ template -#if GENERATING_DOCS +#if BEAST_DOXYGEN void_or_deduced #else typename async_completion< @@ -1647,11 +2640,11 @@ class stream : public detail::stream_base 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< @@ -1661,7 +2654,7 @@ class stream : public detail::stream_base 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,21 +2664,53 @@ class stream : public detail::stream_base template class read_op; template class read_frame_op; + static + void + default_decorate_req(request_type& res) + { + } + + static + void + default_decorate_res(response_type& res) + { + } + void reset(); - http::request - build_request(boost::string_ref const& host, - boost::string_ref const& resource, - std::string& key); + template + void + do_accept(Decorator const& decorator, + error_code& ec); + + template + void + do_accept(http::header const& req, + Decorator const& decorator, error_code& ec); - template - http::response - build_response(http::request const& req); + template + void + do_handshake(response_type* res_p, + boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator, + error_code& ec); + + template + request_type + build_request(std::string& key, + boost::string_ref const& host, + boost::string_ref const& resource, + Decorator const& decorator); + + template + response_type + build_response(request_type const& req, + Decorator const& decorator); - template void - do_response(http::response const& resp, + do_response(http::response_header const& resp, boost::string_ref const& key, error_code& ec); }; diff --git a/include/beast/zlib/deflate_stream.hpp b/include/beast/zlib/deflate_stream.hpp index aae355d06a..7055226328 100644 --- a/include/beast/zlib/deflate_stream.hpp +++ b/include/beast/zlib/deflate_stream.hpp @@ -136,7 +136,7 @@ class deflate_stream 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/inflate_stream.hpp b/include/beast/zlib/inflate_stream.hpp index e6c42021e6..bd8183353d 100644 --- a/include/beast/zlib/inflate_stream.hpp +++ b/include/beast/zlib/inflate_stream.hpp @@ -181,7 +181,7 @@ class inflate_stream `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/test/Jamfile b/test/Jamfile index 018ec76f7f..422f5aec01 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -7,71 +7,6 @@ import os ; -compile config.cpp : : ; -compile core.cpp : : ; -compile http.cpp : : ; -compile version.cpp : : ; -compile websocket.cpp : : ; -compile zlib.cpp : : ; - -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 - ; - -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 - ; - -unit-test bench-tests : - ../extras/beast/unit_test/main.cpp - http/nodejs_parser.cpp - http/parser_bench.cpp - ; - unit-test websocket-tests : ../extras/beast/unit_test/main.cpp websocket/error.cpp @@ -83,21 +18,3 @@ unit-test websocket-tests : websocket/mask.cpp websocket/utf8_checker.cpp ; - -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 - ; diff --git a/test/core/CMakeLists.txt b/test/core/CMakeLists.txt index 766627e3ec..cf1e24cf17 100644 --- a/test/core/CMakeLists.txt +++ b/test/core/CMakeLists.txt @@ -18,6 +18,7 @@ add_executable (core-tests consuming_buffers.cpp dynabuf_readstream.cpp error.cpp + flat_streambuf.cpp handler_alloc.cpp handler_concepts.cpp handler_ptr.cpp diff --git a/test/core/flat_streambuf.cpp b/test/core/flat_streambuf.cpp new file mode 100644 index 0000000000..539be7346d --- /dev/null +++ b/test/core/flat_streambuf.cpp @@ -0,0 +1,171 @@ +// +// 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 + +namespace beast { + +static_assert(is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); + +class flat_streambuf_test : public beast::unit_test::suite +{ +public: + template + static + bool + eq(basic_flat_streambuf const& sb1, + basic_flat_streambuf const& sb2) + { + return to_string(sb1.data()) == to_string(sb2.data()); + } + + void + testSpecialMembers() + { + using boost::asio::buffer; + using boost::asio::buffer_copy; + { + flat_streambuf fb{1, 10}; + BEAST_EXPECT(fb.max_size() == 10); + } + { + flat_streambuf fb{1, 1024}; + BEAST_EXPECT(fb.max_size() == 1024); + } + std::string const s = "Hello, world!"; + for(std::size_t i = 1; i < s.size() - 1; ++i) + { + flat_streambuf fb{1024}; + fb.commit(buffer_copy( + fb.prepare(i), buffer(s))); + fb.commit(buffer_copy( + fb.prepare(s.size() - i), + buffer(s.data() + i, s.size() - i))); + BEAST_EXPECT(to_string(fb.data()) == s); + { + flat_streambuf fb2{fb}; + BEAST_EXPECT(eq(fb2, fb)); + flat_streambuf fb3{std::move(fb2)}; + BEAST_EXPECT(eq(fb3, fb)); + BEAST_EXPECT(! eq(fb2, fb3)); + BEAST_EXPECT(fb2.size() == 0); + } + + using alloc_type = std::allocator; + using streambuf_type = + basic_flat_streambuf; + alloc_type alloc; + { + streambuf_type fba{alloc, 1, 1}; + BEAST_EXPECT(fba.max_size() == 1); + } + { + streambuf_type fba{alloc, 1024, 1024}; + BEAST_EXPECT(fba.max_size() == 1024); + } + { + streambuf_type fb2{fb}; + BEAST_EXPECT(eq(fb2, fb)); + streambuf_type fb3{std::move(fb2)}; + BEAST_EXPECT(eq(fb3, fb)); + BEAST_EXPECT(! eq(fb2, fb3)); + BEAST_EXPECT(fb2.size() == 0); + } + { + streambuf_type fb2{fb, alloc}; + BEAST_EXPECT(eq(fb2, fb)); + streambuf_type fb3{std::move(fb2), alloc}; + BEAST_EXPECT(eq(fb3, fb)); + BEAST_EXPECT(! eq(fb2, fb3)); + BEAST_EXPECT(fb2.size() == 0); + } + } + } + + void + testStream() + { + using boost::asio::buffer_size; + + flat_streambuf fb{1, 100}; + BEAST_EXPECT(fb.size() == 0); + BEAST_EXPECT(fb.capacity() == 1); + + BEAST_EXPECT(buffer_size(fb.prepare(100)) == 100); + BEAST_EXPECT(fb.size() == 0); + BEAST_EXPECT(fb.capacity() == 100); + + fb.commit(20); + BEAST_EXPECT(fb.size() == 20); + BEAST_EXPECT(fb.capacity() == 100); + + fb.consume(5); + BEAST_EXPECT(fb.size() == 15); + BEAST_EXPECT(fb.capacity() == 95); + + fb.prepare(80); + fb.commit(80); + BEAST_EXPECT(fb.size() == 95); + BEAST_EXPECT(fb.capacity() == 100); + + fb.shrink_to_fit(); + BEAST_EXPECT(fb.size() == 95); + BEAST_EXPECT(fb.capacity() == 95); + } + + void + testPrepare() + { + flat_streambuf fb{1}; + fb.prepare(20); + BEAST_EXPECT(fb.capacity() == 20); + fb.commit(10); + BEAST_EXPECT(fb.capacity() == 20); + fb.consume(4); + BEAST_EXPECT(fb.capacity() == 16); + fb.prepare(14); + BEAST_EXPECT(fb.size() == 6); + BEAST_EXPECT(fb.capacity() == 20); + fb.consume(10); + BEAST_EXPECT(fb.size() == 0); + BEAST_EXPECT(fb.capacity() == 20); + } + + void + testMax() + { + flat_streambuf fb{1, 1}; + try + { + fb.prepare(2); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + + void + run() override + { + testSpecialMembers(); + testStream(); + testPrepare(); + testMax(); + } +}; + +BEAST_DEFINE_TESTSUITE(flat_streambuf,core,beast); + +} // beast diff --git a/test/core/streambuf.cpp b/test/core/streambuf.cpp index eb295161f6..ee16267273 100644 --- a/test/core/streambuf.cpp +++ b/test/core/streambuf.cpp @@ -172,8 +172,6 @@ class basic_streambuf_test : public beast::unit_test::suite 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) { @@ -263,7 +261,6 @@ class basic_streambuf_test : public beast::unit_test::suite void testCommit() { - using boost::asio::buffer_size; streambuf sb(2); sb.prepare(2); sb.prepare(5); @@ -273,7 +270,6 @@ class basic_streambuf_test : public beast::unit_test::suite void testConsume() { - using boost::asio::buffer_size; streambuf sb(1); expect_size(5, sb.prepare(5)); sb.commit(3); @@ -285,7 +281,6 @@ class basic_streambuf_test : public beast::unit_test::suite 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); diff --git a/test/http/CMakeLists.txt b/test/http/CMakeLists.txt index 5505e37dc1..c262edddca 100644 --- a/test/http/CMakeLists.txt +++ b/test/http/CMakeLists.txt @@ -8,19 +8,18 @@ add_executable (http-tests ${BEAST_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 + basic_parser.cpp concepts.cpp - empty_body.cpp + design.cpp + error.cpp fields.cpp - header_parser_v1.cpp + header_parser.cpp message.cpp - parse.cpp - parse_error.cpp - parser_v1.cpp + message_parser.cpp read.cpp reason.cpp rfc7230.cpp diff --git a/test/http/basic_parser.cpp b/test/http/basic_parser.cpp new file mode 100644 index 0000000000..9c73f3181a --- /dev/null +++ b/test/http/basic_parser.cpp @@ -0,0 +1,960 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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 + +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 + static + boost::asio::const_buffers_1 + buf(char const (&s)[N]) + { + return {s, N-1}; + } + + template< + bool isRequest, bool isDirect, class Derived> + static + std::size_t + feed(boost::asio::const_buffer buffer, + basic_parser& parser, + error_code& ec) + { + using boost::asio::const_buffers_1; + std::size_t used = 0; + for(;;) + { + auto const n = parser.write( + const_buffers_1{buffer}, ec); + if(ec) + return 0; + if(n == 0) + break; + buffer = buffer + n; + used += n; + if(parser.is_complete()) + break; + if(buffer_size(buffer) == 0) + break; + } + return used; + } + + template + static + std::size_t + feed(ConstBufferSequence const& buffers, + basic_parser& parser, + error_code& ec) + { + using boost::asio::buffer_size; + consuming_buffers< + ConstBufferSequence> cb{buffers}; + std::size_t used = 0; + for(;;) + { + auto const n = + parser.write(cb, ec); + if(ec) + return 0; + if(n == 0) + break; + cb.consume(n); + used += n; + if(parser.is_complete()) + break; + if(buffer_size(cb) == 0) + break; + } + return used; + } + + template< + bool isRequest, bool isDirect, class Derived> + static + std::size_t + feed(boost::asio::const_buffers_1 buffers, + basic_parser& parser, + error_code& ec) + { + return feed(*buffers.begin(), parser, ec); + } + + template + void + good(boost::string_ref const& s, + Pred const& pred, bool skipBody = false) + { + using boost::asio::buffer; + test_parser p; + if(skipBody) + p.skip_body(); + error_code ec; + auto const n = feed(buffer( + s.data(), s.size()), p, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + if(! BEAST_EXPECT(n == s.size())) + return; + if(p.state() == parse_state::body_to_eof) + p.write_eof(ec); + if(BEAST_EXPECTS(! ec, ec.message())) + pred(p); + } + + template + void + good(boost::string_ref const& s) + { + good(s, + [](test_parser const&) + { + }); + } + + template + void + bad(boost::string_ref const& s, + error_code const& ev, bool skipBody = false) + { + using boost::asio::buffer; + test_parser p; + if(skipBody) + p.skip_body(); + error_code ec; + feed(buffer( + s.data(), s.size()), p, ec); + if(! ec && ev) + p.write_eof(ec); + BEAST_EXPECTS(ec == ev, ec.message()); + } + + void + testFlatten() + { + using boost::asio::buffer; + { + std::string const s = + "GET / HTTP/1.1\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*"; + for(std::size_t i = 1; + i < s.size() - 1; ++i) + { + auto const b1 = + buffer(s.data(), i); + auto const b2 = buffer( + s.data() + i, s.size() - i); + test_parser p; + error_code ec; + feed(b1, p, ec); + BEAST_EXPECTS(! ec, ec.message()); + feed(buffer_cat(b1, b2), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_complete()); + } + } + { + std::string const s = + "HTTP/1.1 200 OK\r\n" + "\r\n"; + for(std::size_t i = 1; + i < s.size() - 1; ++i) + { + auto const b1 = + buffer(s.data(), i); + auto const b2 = buffer( + s.data() + i, s.size() - i); + test_parser p; + error_code ec; + feed(b1, p, ec); + BEAST_EXPECTS(! ec, ec.message()); + ec = {}; + feed(buffer_cat(b1, b2), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + p.write_eof(ec); + } + } + } + + // Check that all callbacks are invoked + void + testCallbacks() + { + using boost::asio::buffer; + { + test_parser 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" + "*"; + feed(buffer(s), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_complete()); + BEAST_EXPECT(p.got_on_begin); + BEAST_EXPECT(p.got_on_field); + BEAST_EXPECT(p.got_on_header); + BEAST_EXPECT(p.got_on_body); + BEAST_EXPECT(! p.got_on_chunk); + BEAST_EXPECT(p.got_on_complete); + } + { + test_parser 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" + "*"; + feed(buffer(s), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_complete()); + BEAST_EXPECT(p.got_on_begin); + BEAST_EXPECT(p.got_on_field); + BEAST_EXPECT(p.got_on_header); + BEAST_EXPECT(p.got_on_body); + BEAST_EXPECT(! p.got_on_chunk); + BEAST_EXPECT(p.got_on_complete); + } + } + + void + testRequestLine() + { + 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", expect_version{*this, 10}); + good("G / HTTP/1.1\r\n\r\n", expect_version{*this, 11}); + // VFALCO TODO various forms of good request-target (uri) + good("GET / HTTP/0.1\r\n\r\n", expect_version{*this, 1}); + good("GET / HTTP/2.3\r\n\r\n", expect_version{*this, 23}); + good("GET / HTTP/4.5\r\n\r\n", expect_version{*this, 45}); + good("GET / HTTP/6.7\r\n\r\n", expect_version{*this, 67}); + good("GET / HTTP/8.9\r\n\r\n", expect_version{*this, 89}); + + bad("\tGET / HTTP/1.0\r\n" "\r\n", error::bad_method); + bad("GET\x01 / HTTP/1.0\r\n" "\r\n", error::bad_method); + bad("GET / HTTP/1.0\r\n" "\r\n", error::bad_path); + bad("GET \x01 HTTP/1.0\r\n" "\r\n", error::bad_path); + bad("GET /\x01 HTTP/1.0\r\n" "\r\n", error::bad_path); + // VFALCO TODO various forms of bad request-target (uri) + bad("GET / HTTP/1.0\r\n" "\r\n", error::bad_version); + bad("GET / _TTP/1.0\r\n" "\r\n", error::bad_version); + bad("GET / H_TP/1.0\r\n" "\r\n", error::bad_version); + bad("GET / HT_P/1.0\r\n" "\r\n", error::bad_version); + bad("GET / HTT_/1.0\r\n" "\r\n", error::bad_version); + bad("GET / HTTP_1.0\r\n" "\r\n", error::bad_version); + bad("GET / HTTP/01.2\r\n" "\r\n", error::bad_version); + bad("GET / HTTP/3.45\r\n" "\r\n", error::bad_version); + bad("GET / HTTP/67.89\r\n" "\r\n", error::bad_version); + bad("GET / HTTP/x.0\r\n" "\r\n", error::bad_version); + bad("GET / HTTP/1.x\r\n" "\r\n", error::bad_version); + bad("GET / HTTP/1.0 \r\n" "\r\n", error::bad_version); + bad("GET / HTTP/1_0\r\n" "\r\n", error::bad_version); + bad("GET / HTTP/1.0\n\r\n" "\r\n", error::bad_version); + bad("GET / HTTP/1.0\n\r\r\n" "\r\n", error::bad_line_ending); + bad("GET / HTTP/1.0\r\r\n" "\r\n", error::bad_line_ending); + } + + void + testStatusLine() + { + good("HTTP/0.1 200 OK\r\n" "\r\n", expect_version{*this, 1}); + good("HTTP/2.3 200 OK\r\n" "\r\n", expect_version{*this, 23}); + good("HTTP/4.5 200 OK\r\n" "\r\n", expect_version{*this, 45}); + good("HTTP/6.7 200 OK\r\n" "\r\n", expect_version{*this, 67}); + good("HTTP/8.9 200 OK\r\n" "\r\n", expect_version{*this, 89}); + good("HTTP/1.0 000 OK\r\n" "\r\n", expect_status{*this, 0}); + good("HTTP/1.1 012 OK\r\n" "\r\n", expect_status{*this, 12}); + good("HTTP/1.0 345 OK\r\n" "\r\n", expect_status{*this, 345}); + good("HTTP/1.0 678 OK\r\n" "\r\n", expect_status{*this, 678}); + good("HTTP/1.0 999 OK\r\n" "\r\n", expect_status{*this, 999}); + good("HTTP/1.0 200 \tX\r\n" "\r\n", expect_version{*this, 10}); + good("HTTP/1.1 200 X\r\n" "\r\n", expect_version{*this, 11}); + 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", error::bad_line_ending); + bad("\nHTTP/1.0 200 OK\r\n" "\r\n", error::bad_version); + bad(" HTTP/1.0 200 OK\r\n" "\r\n", error::bad_version); + bad("_TTP/1.0 200 OK\r\n" "\r\n", error::bad_version); + bad("H_TP/1.0 200 OK\r\n" "\r\n", error::bad_version); + bad("HT_P/1.0 200 OK\r\n" "\r\n", error::bad_version); + bad("HTT_/1.0 200 OK\r\n" "\r\n", error::bad_version); + bad("HTTP_1.0 200 OK\r\n" "\r\n", error::bad_version); + bad("HTTP/01.2 200 OK\r\n" "\r\n", error::bad_version); + bad("HTTP/3.45 200 OK\r\n" "\r\n", error::bad_version); + bad("HTTP/67.89 200 OK\r\n" "\r\n", error::bad_version); + bad("HTTP/x.0 200 OK\r\n" "\r\n", error::bad_version); + bad("HTTP/1.x 200 OK\r\n" "\r\n", error::bad_version); + bad("HTTP/1_0 200 OK\r\n" "\r\n", error::bad_version); + bad("HTTP/1.0 200 OK\r\n" "\r\n", error::bad_status); + bad("HTTP/1.0 0 OK\r\n" "\r\n", error::bad_status); + bad("HTTP/1.0 12 OK\r\n" "\r\n", error::bad_status); + bad("HTTP/1.0 3456 OK\r\n" "\r\n", error::bad_status); + bad("HTTP/1.0 200\r\n" "\r\n", error::bad_status); + bad("HTTP/1.0 200 \n\r\n" "\r\n", error::bad_reason); + bad("HTTP/1.0 200 \x01\r\n" "\r\n", error::bad_reason); + bad("HTTP/1.0 200 \x7f\r\n" "\r\n", error::bad_reason); + bad("HTTP/1.0 200 OK\n\r\n" "\r\n", error::bad_reason); + bad("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"; + }; + 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"), error::bad_field); + bad(m("\tf: v\r\n"), error::bad_field); + bad(m("f : v\r\n"), error::bad_field); + bad(m("f\t: v\r\n"), error::bad_field); + bad(m("f: \n\r\n"), error::bad_value); + bad(m("f: v\r \r\n"), error::bad_line_ending); + bad(m("f: \r v\r\n"), error::bad_line_ending); + bad("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 + + good(cn("close\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn(",close\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn(" close\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn("\tclose\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn("close,\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn("close\t\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn("close\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn(" ,\t,,close,, ,\t,,\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn("\r\n close\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn("close\r\n \r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn("any,close\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn("close,any\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn("any\r\n ,close\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn("close\r\n ,any\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn("close,close\r\n"), expect_flags{*this, parse_flag::connection_close}); // weird but allowed + + good(cn("keep-alive\r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + good(cn("keep-alive \r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + good(cn("keep-alive\t \r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + good(cn("keep-alive\t ,x\r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + good(cn("\r\n keep-alive \t\r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + good(cn("keep-alive \r\n \t \r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + good(cn("keep-alive\r\n \r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + + good(cn("upgrade\r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + good(cn("upgrade \r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + good(cn("upgrade\t \r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + good(cn("upgrade\t ,x\r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + good(cn("\r\n upgrade \t\r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + good(cn("upgrade \r\n \t \r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + good(cn("upgrade\r\n \r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + + // VFALCO What's up with these? + //good(cn("close,keep-alive\r\n"), expect_flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive}); + good(cn("upgrade,keep-alive\r\n"), expect_flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive}); + good(cn("upgrade,\r\n keep-alive\r\n"), expect_flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive}); + //good(cn("close,keep-alive,upgrade\r\n"), expect_flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive | parse_flag::connection_upgrade}); + + good("GET / HTTP/1.1\r\n\r\n", expect_keepalive(*this, true)); + good("GET / HTTP/1.0\r\n\r\n", expect_keepalive(*this, false)); + good("GET / HTTP/1.0\r\n" + "Connection: keep-alive\r\n\r\n", expect_keepalive(*this, true)); + good("GET / HTTP/1.1\r\n" + "Connection: close\r\n\r\n", expect_keepalive(*this, false)); + + good(cn("x\r\n"), expect_flags{*this, 0}); + good(cn("x,y\r\n"), expect_flags{*this, 0}); + good(cn("x ,y\r\n"), expect_flags{*this, 0}); + good(cn("x\t,y\r\n"), expect_flags{*this, 0}); + good(cn("keep\r\n"), expect_flags{*this, 0}); + good(cn(",keep\r\n"), expect_flags{*this, 0}); + good(cn(" keep\r\n"), expect_flags{*this, 0}); + good(cn("\tnone\r\n"), expect_flags{*this, 0}); + good(cn("keep,\r\n"), expect_flags{*this, 0}); + good(cn("keep\t\r\n"), expect_flags{*this, 0}); + good(cn("keep\r\n"), expect_flags{*this, 0}); + good(cn(" ,\t,,keep,, ,\t,,\r\n"), expect_flags{*this, 0}); + good(cn("\r\n keep\r\n"), expect_flags{*this, 0}); + good(cn("keep\r\n \r\n"), expect_flags{*this, 0}); + good(cn("closet\r\n"), expect_flags{*this, 0}); + good(cn(",closet\r\n"), expect_flags{*this, 0}); + good(cn(" closet\r\n"), expect_flags{*this, 0}); + good(cn("\tcloset\r\n"), expect_flags{*this, 0}); + good(cn("closet,\r\n"), expect_flags{*this, 0}); + good(cn("closet\t\r\n"), expect_flags{*this, 0}); + good(cn("closet\r\n"), expect_flags{*this, 0}); + good(cn(" ,\t,,closet,, ,\t,,\r\n"), expect_flags{*this, 0}); + good(cn("\r\n closet\r\n"), expect_flags{*this, 0}); + good(cn("closet\r\n \r\n"), expect_flags{*this, 0}); + good(cn("clog\r\n"), expect_flags{*this, 0}); + good(cn("key\r\n"), expect_flags{*this, 0}); + good(cn("uptown\r\n"), expect_flags{*this, 0}); + good(cn("keeper\r\n \r\n"), expect_flags{*this, 0}); + good(cn("keep-alively\r\n \r\n"), expect_flags{*this, 0}); + good(cn("up\r\n \r\n"), expect_flags{*this, 0}); + good(cn("upgrader\r\n \r\n"), expect_flags{*this, 0}); + good(cn("none\r\n"), expect_flags{*this, 0}); + good(cn("\r\n none\r\n"), expect_flags{*this, 0}); + + good(m("ConnectioX: close\r\n"), expect_flags{*this, 0}); + good(m("Condor: close\r\n"), expect_flags{*this, 0}); + good(m("Connect: close\r\n"), expect_flags{*this, 0}); + good(m("Connections: close\r\n"), expect_flags{*this, 0}); + + good(m("Proxy-Connection: close\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(m("Proxy-Connection: keep-alive\r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + good(m("Proxy-Connection: upgrade\r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + good(m("Proxy-ConnectioX: none\r\n"), expect_flags{*this, 0}); + good(m("Proxy-Connections: 1\r\n"), expect_flags{*this, 0}); + good(m("Proxy-Connotes: see-also\r\n"), expect_flags{*this, 0}); + + bad(cn("[\r\n"), error::bad_value); + bad(cn("close[\r\n"), error::bad_value); + bad(cn("close [\r\n"), error::bad_value); + bad(cn("close, upgrade [\r\n"), error::bad_value); + bad(cn("upgrade[]\r\n"), error::bad_value); + bad(cn("keep\r\n -alive\r\n"), error::bad_value); + bad(cn("keep-alive[\r\n"), error::bad_value); + bad(cn("keep-alive []\r\n"), error::bad_value); + bad(cn("no[ne]\r\n"), error::bad_value); + } + + void + testContentLengthField() + { + auto const length = + [&](std::string const& s, std::uint64_t v) + { + good(s, + [&](test_parser const& p) + { + BEAST_EXPECT(p.content_length()); + BEAST_EXPECT(p.content_length() && *p.content_length() == v); + }, true); + }; + 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); + + // VFALCO Investigate this failure + //length(c("42\r\n \t \r\n"), 42); + + good(m("Content-LengtX: 0\r\n"), expect_flags{*this, 0}); + good(m("Content-Lengths: many\r\n"), expect_flags{*this, 0}); + good(m("Content: full\r\n"), expect_flags{*this, 0}); + + bad(c("\r\n"), error::bad_content_length); + bad(c("18446744073709551616\r\n"), error::bad_content_length); + bad(c("0 0\r\n"), error::bad_content_length); + bad(c("0 1\r\n"), error::bad_content_length); + bad(c(",\r\n"), error::bad_content_length); + bad(c("0,\r\n"), error::bad_content_length); + bad(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"; + }; + good(ce("chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce("chunked \r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce("chunked\t\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce("chunked \t\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce(" chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce("\tchunked\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce("chunked,\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce("chunked ,\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce("chunked, \r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce(",chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce(", chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce(" ,chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce("chunked\r\n \r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce("\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce(",\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce("\r\n ,chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce(",\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce("gzip, chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce("gzip, chunked \r\n"), expect_flags{*this, parse_flag::chunked}); + good(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 + //good(ce("custom;key=\",chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + + good(te("gzip\r\n"), expect_flags{*this, 0}); + good(te("chunked, gzip\r\n"), expect_flags{*this, 0}); + good(te("chunked\r\n , gzip\r\n"), expect_flags{*this, 0}); + good(te("chunked,\r\n gzip\r\n"), expect_flags{*this, 0}); + good(te("chunked,\r\n ,gzip\r\n"), expect_flags{*this, 0}); + good(te("bigchunked\r\n"), expect_flags{*this, 0}); + good(te("chunk\r\n ked\r\n"), expect_flags{*this, 0}); + good(te("bar\r\n ley chunked\r\n"), expect_flags{*this, 0}); + good(te("barley\r\n chunked\r\n"), expect_flags{*this, 0}); + + good(m("Transfer-EncodinX: none\r\n"), expect_flags{*this, 0}); + good(m("Transfer-Encodings: 2\r\n"), expect_flags{*this, 0}); + good(m("Transfer-Encoded: false\r\n"), expect_flags{*this, 0}); + + bad( + "HTTP/1.1 200 OK\r\n" + "Content-Length: 1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n", error::bad_transfer_encoding, true); + } + + void + testUpgradeField() + { + auto const m = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\n" + s + "\r\n"; + }; + good(m("Upgrade:\r\n"), expect_flags{*this, parse_flag::upgrade}); + good(m("Upgrade: \r\n"), expect_flags{*this, parse_flag::upgrade}); + good(m("Upgrade: yes\r\n"), expect_flags{*this, parse_flag::upgrade}); + + good(m("Up: yes\r\n"), expect_flags{*this, 0}); + good(m("UpgradX: none\r\n"), expect_flags{*this, 0}); + good(m("Upgrades: 2\r\n"), expect_flags{*this, 0}); + good(m("Upsample: 4x\r\n"), expect_flags{*this, 0}); + + good( + "GET / HTTP/1.1\r\n" + "Connection: upgrade\r\n" + "Upgrade: WebSocket\r\n" + "\r\n", + [&](test_parser const& p) + { + BEAST_EXPECT(p.is_upgrade()); + }); + } + + void testBody() + { + using boost::asio::buffer; + good( + "GET / HTTP/1.1\r\n" + "Content-Length: 1\r\n" + "\r\n" + "1", + expect_body(*this, "1")); + + good( + "HTTP/1.0 200 OK\r\n" + "\r\n" + "hello", + expect_body(*this, "hello")); + + // write the body in 3 pieces + { + error_code ec; + test_parser p; + feed(buffer_cat( + buf("GET / HTTP/1.1\r\n" + "Content-Length: 10\r\n" + "\r\n"), + buf("12"), + buf("345"), + buf("67890")), + p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_complete()); + } + + // 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_complete()); + } + { + 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_complete()); + } + + // 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_complete()); + BEAST_EXPECT(p.state() == parse_state::body_to_eof); + feed(buf( + "hello" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(! p.is_complete()); + BEAST_EXPECT(p.state() == parse_state::body_to_eof); + p.write_eof(ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_complete()); + } + + // 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_complete()); + } + + // 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_complete()); + feed(buf( + "0\r\n\r\n" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_complete()); + } + + // 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_complete()); + } + + // 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_complete()); + } + + bad( + "GET / HTTP/1.1\r\n" + "Content-Length: 1\r\n" + "\r\n", + error::partial_message); + } + + template + void + check_header( + test_parser const& p) + { + BEAST_EXPECT(p.got_on_begin); + BEAST_EXPECT(p.got_on_field); + BEAST_EXPECT(p.got_on_header); + BEAST_EXPECT(! p.got_on_body); + BEAST_EXPECT(! p.got_on_chunk); + BEAST_EXPECT(! p.got_on_end_body); + BEAST_EXPECT(! p.got_on_complete); + BEAST_EXPECT(p.state() != parse_state::header); + } + + void + testSplit() + { +#if 0 + streambuf sb; + sb << + "POST / HTTP/1.1\r\n" + "Content-Length: 5\r\n" + "\r\n" + "*****"; + error_code ec; + test_parser p; + p.pause(); + auto n = feed(sb.data(), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.got_on_begin); + BEAST_EXPECT(p.got_on_field); + BEAST_EXPECT(p.got_on_header); + BEAST_EXPECT(! p.got_on_body); + BEAST_EXPECT(! p.got_on_chunk); + BEAST_EXPECT(! p.got_on_complete); + BEAST_EXPECT(p.state() != parse_state::header); + BEAST_EXPECT(! p.is_complete()); + BEAST_EXPECT(p.body.empty()); + sb.consume(n); + p.resume(); + n = feed(sb.data(), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.got_on_begin); + BEAST_EXPECT(p.got_on_field); + BEAST_EXPECT(p.got_on_header); + BEAST_EXPECT(p.got_on_body); + BEAST_EXPECT(! p.got_on_chunk); + BEAST_EXPECT(p.got_on_complete); + BEAST_EXPECT(p.is_complete()); + BEAST_EXPECT(p.body == "*****"); +#endif + } + + void + run() override + { + testFlatten(); + testCallbacks(); + testRequestLine(); + testStatusLine(); + testFields(); + testConnectionField(); + testContentLengthField(); + testTransferEncodingField(); + testUpgradeField(); + testBody(); + testSplit(); + } +}; + +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/http/design.cpp b/test/http/design.cpp new file mode 100644 index 0000000000..1d1efa990d --- /dev/null +++ b/test/http/design.cpp @@ -0,0 +1,534 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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 +#include + +namespace beast { +namespace http { + +class design_test + : public beast::unit_test::suite + , public beast::test::enable_yield_to +{ +public: + //-------------------------------------------------------------------------- + /* + Read a message with a direct Reader Body. + */ + struct direct_body + { + using value_type = std::string; + + class reader + { + value_type& body_; + std::size_t len_ = 0; + + public: + static bool constexpr is_direct = true; + + using mutable_buffers_type = + boost::asio::mutable_buffers_1; + + template + explicit + reader(message& m) + : body_(m.body) + { + } + + void + init() + { + } + + void + init(std::uint64_t content_length) + { + if(content_length > + (std::numeric_limits::max)()) + throw std::length_error( + "Content-Length max exceeded"); + body_.reserve(static_cast< + std::size_t>(content_length)); + } + + mutable_buffers_type + prepare(std::size_t n) + { + body_.resize(len_ + n); + return {&body_[len_], n}; + } + + void + commit(std::size_t n) + { + if(body_.size() > len_ + n) + body_.resize(len_ + n); + len_ = body_.size(); + } + + void + finish() + { + body_.resize(len_); + } + }; + }; + + void + testDirectBody() + { + // Content-Length + { + test::string_istream is{ios_, + "GET / HTTP/1.1\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*" + }; + message m; + flat_streambuf sb{1024}; + read(is, sb, m); + BEAST_EXPECT(m.body == "*"); + } + + // end of file + { + test::string_istream is{ios_, + "HTTP/1.1 200 OK\r\n" + "\r\n" // 19 byte header + "*" + }; + message m; + flat_streambuf sb{20}; + read(is, sb, m); + BEAST_EXPECT(m.body == "*"); + } + + // chunked + { + test::string_istream is{ios_, + "GET / HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "1\r\n" + "*\r\n" + "0\r\n\r\n" + }; + message m; + flat_streambuf sb{10}; + read(is, sb, m); + BEAST_EXPECT(m.body == "*"); + } + } + + //-------------------------------------------------------------------------- + /* + Read a message with an indirect Reader Body. + */ + struct indirect_body + { + using value_type = std::string; + + class reader + { + value_type& body_; + + public: + static bool constexpr is_direct = false; + + using mutable_buffers_type = + boost::asio::null_buffers; + + template + explicit + reader(message& m) + : body_(m.body) + { + } + + void + init(error_code& ec) + { + } + + void + init(std::uint64_t content_length, + error_code& ec) + { + } + + void + write(boost::string_ref const& s, + error_code& ec) + { + body_.append(s.data(), s.size()); + } + + void + finish(error_code& ec) + { + } + }; + }; + + void + testIndirectBody() + { + // Content-Length + { + test::string_istream is{ios_, + "GET / HTTP/1.1\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*" + }; + message m; + flat_streambuf sb{1024}; + read(is, sb, m); + BEAST_EXPECT(m.body == "*"); + } + + // end of file + { + test::string_istream is{ios_, + "HTTP/1.1 200 OK\r\n" + "\r\n" // 19 byte header + "*" + }; + message m; + flat_streambuf sb{20}; + read(is, sb, m); + BEAST_EXPECT(m.body == "*"); + } + + + // chunked + { + test::string_istream is{ios_, + "GET / HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "1\r\n" + "*\r\n" + "0\r\n\r\n" + }; + message m; + flat_streambuf sb{1024}; + read(is, sb, m); + BEAST_EXPECT(m.body == "*"); + } + } + + //-------------------------------------------------------------------------- + /* + Read a message header and manually read the body. + */ + void + testManualBody() + { + // Content-Length + { + test::string_istream is{ios_, + "GET / HTTP/1.1\r\n" + "Content-Length: 5\r\n" + "\r\n" // 37 byte header + "*****" + }; + header_parser p; + flat_streambuf sb{38}; + auto const bytes_used = + read_some(is, sb, p); + sb.consume(bytes_used); + BEAST_EXPECT(p.size() == 5); + BEAST_EXPECT(sb.size() < 5); + sb.commit(boost::asio::read( + is, sb.prepare(5 - sb.size()))); + BEAST_EXPECT(sb.size() == 5); + } + + // end of file + { + test::string_istream is{ios_, + "HTTP/1.1 200 OK\r\n" + "\r\n" // 19 byte header + "*****" + }; + header_parser p; + flat_streambuf sb{20}; + auto const bytes_used = + read_some(is, sb, p); + sb.consume(bytes_used); + BEAST_EXPECT(p.state() == + parse_state::body_to_eof); + BEAST_EXPECT(sb.size() < 5); + sb.commit(boost::asio::read( + is, sb.prepare(5 - sb.size()))); + BEAST_EXPECT(sb.size() == 5); + } + } + + //-------------------------------------------------------------------------- + /* + Read a header, check for Expect: 100-continue, + then conditionally read the body. + */ + void + testExpect100Continue() + { + { + test::string_istream is{ios_, + "GET / HTTP/1.1\r\n" + "Expect: 100-continue\r\n" + "Content-Length: 5\r\n" + "\r\n" + "*****" + }; + + header_parser p; + flat_streambuf sb{40}; + auto const bytes_used = + read_some(is, sb, p); + sb.consume(bytes_used); + BEAST_EXPECT(p.got_header()); + BEAST_EXPECT( + p.get().fields["Expect"] == + "100-continue"); + message_parser< + true, string_body, fields> p1{ + std::move(p)}; + read(is, sb, p1); + BEAST_EXPECT( + p1.get().body == "*****"); + } + } + + //-------------------------------------------------------------------------- + /* + Efficiently relay a message from one stream to another + */ + template< + bool isRequest, + class SyncWriteStream, + class DynamicBuffer, + class SyncReadStream> + void + relay( + SyncWriteStream& out, + DynamicBuffer& sb, + SyncReadStream& in) + { + flat_streambuf buffer{4096, 4096}; // 4K size, 4K limit + header_parser parser; + error_code ec; + do + { + auto const state0 = parser.state(); + auto const bytes_used = + read_some(in, buffer, parser, ec); + BEAST_EXPECTS(! ec, ec.message()); + switch(state0) + { + case parse_state::header: + { + BEAST_EXPECT(parser.got_header()); + write(out, parser.get()); + break; + } + + case parse_state::chunk_header: + { + // inspect parser.chunk_extension() here + if(parser.is_complete()) + boost::asio::write(out, + chunk_encode_final()); + break; + } + + case parse_state::body: + case parse_state::body_to_eof: + case parse_state::chunk_body: + { + if(! parser.is_complete()) + { + auto const body = parser.body(); + boost::asio::write(out, chunk_encode( + false, boost::asio::buffer( + body.data(), body.size()))); + } + break; + } + + case parse_state::complete: + break; + } + buffer.consume(bytes_used); + } + while(! parser.is_complete()); + } + + void + testRelay() + { + // Content-Length + { + test::string_istream is{ios_, + "GET / HTTP/1.1\r\n" + "Content-Length: 5\r\n" + "\r\n" // 37 byte header + "*****", + 3 // max_read + }; + test::string_ostream os{ios_}; + flat_streambuf sb{16}; + relay(os, sb, is); + } + + // end of file + { + test::string_istream is{ios_, + "HTTP/1.1 200 OK\r\n" + "\r\n" // 19 byte header + "*****", + 3 // max_read + }; + test::string_ostream os{ios_}; + flat_streambuf sb{16}; + relay(os, sb, is); + } + + // chunked + { + test::string_istream is{ios_, + "GET / HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "5;x;y=1;z=\"-\"\r\n*****\r\n" + "3\r\n---\r\n" + "1\r\n+\r\n" + "0\r\n\r\n", + 2 // max_read + }; + test::string_ostream os{ios_}; + flat_streambuf sb{16}; + relay(os, sb, is); + } + } + + //-------------------------------------------------------------------------- + /* + Read the request header, then read the request body content using + a fixed-size buffer, i.e. read the body in chunks of 4k for instance. + The end of the body should be indicated somehow and chunk-encoding + should be decoded by beast. + */ + template + void + doFixedRead(SyncReadStream& stream, BodyCallback const& cb) + { + flat_streambuf buffer{4096, 4096}; // 4K size, 4K limit + header_parser parser; + std::size_t bytes_used; + bytes_used = read_some(stream, buffer, parser); + BEAST_EXPECT(parser.got_header()); + buffer.consume(bytes_used); + do + { + bytes_used = + read_some(stream, buffer, parser); + if(! parser.body().empty()) + cb(parser.body()); + buffer.consume(bytes_used); + } + while(! parser.is_complete()); + } + + struct bodyHandler + { + void + operator()(boost::string_ref const& body) const + { + // called for each piece of the body, + } + }; + + void + testFixedRead() + { + using boost::asio::buffer; + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + + // Content-Length + { + test::string_istream is{ios_, + "GET / HTTP/1.1\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*" + }; + doFixedRead(is, bodyHandler{}); + } + + // end of file + { + test::string_istream is{ios_, + "HTTP/1.1 200 OK\r\n" + "\r\n" // 19 byte header + "*****" + }; + doFixedRead(is, bodyHandler{}); + } + + // chunked + { + test::string_istream is{ios_, + "GET / HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "5;x;y=1;z=\"-\"\r\n*****\r\n" + "3\r\n---\r\n" + "1\r\n+\r\n" + "0\r\n\r\n", + 2 // max_read + }; + doFixedRead(is, bodyHandler{}); + } + } + + //-------------------------------------------------------------------------- + + void + run() + { + testDirectBody(); + testIndirectBody(); + testManualBody(); + testExpect100Continue(); + testRelay(); + testFixedRead(); + } +}; + +BEAST_DEFINE_TESTSUITE(design,http,beast); + +} // http +} // beast diff --git a/test/http/empty_body.cpp b/test/http/empty_body.cpp deleted file mode 100644 index fa190ed38e..0000000000 --- a/test/http/empty_body.cpp +++ /dev/null @@ -1,9 +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 diff --git a/test/http/error.cpp b/test/http/error.cpp new file mode 100644 index 0000000000..1dcf99b6a1 --- /dev/null +++ b/test/http/error.cpp @@ -0,0 +1,59 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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("http", error::end_of_stream); + check("http", error::partial_message); + check("http", error::buffer_overflow); + check("http", error::bad_line_ending); + check("http", error::bad_method); + check("http", error::bad_path); + check("http", error::bad_version); + check("http", error::bad_status); + check("http", error::bad_reason); + check("http", error::bad_field); + check("http", error::bad_value); + check("http", error::bad_content_length); + check("http", error::bad_transfer_encoding); + check("http", error::bad_chunk); + } +}; + +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/header_parser.cpp b/test/http/header_parser.cpp new file mode 100644 index 0000000000..473355c39e --- /dev/null +++ b/test/http/header_parser.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 +#include +#include +#include + +namespace beast { +namespace http { + +class header_parser_test + : public beast::unit_test::suite + , public test::enable_yield_to +{ +public: + void + testParse() + { + { + test::string_istream is{ios_, + "GET / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "\r\n" + }; + flat_streambuf db{1024}; + header_parser p; + read_some(is, db, p); + BEAST_EXPECT(p.is_complete()); + } + { + test::string_istream is{ios_, + "POST / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*" + }; + flat_streambuf db{1024}; + header_parser p; + read_some(is, db, p); + BEAST_EXPECT(! p.is_complete()); + BEAST_EXPECT(p.state() == parse_state::body); + } + } + + void + run() override + { + testParse(); + } +}; + +BEAST_DEFINE_TESTSUITE(header_parser,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..54f49f1b7c 100644 --- a/test/http/message.cpp +++ b/test/http/message.cpp @@ -8,7 +8,7 @@ // Test that header file is self-contained. #include -#include +#include #include #include #include @@ -195,7 +195,7 @@ class message_test : public beast::unit_test::suite void testFreeFunctions() { { - request m; + request m; m.method = "GET"; m.url = "/"; m.version = 11; @@ -213,7 +213,7 @@ class message_test : public beast::unit_test::suite void testPrepare() { - request m; + request m; m.version = 10; BEAST_EXPECT(! is_upgrade(m)); m.fields.insert("Transfer-Encoding", "chunked"); diff --git a/test/http/message_fuzz.hpp b/test/http/message_fuzz.hpp index c0780a08d0..9317add0fd 100644 --- a/test/http/message_fuzz.hpp +++ b/test/http/message_fuzz.hpp @@ -9,7 +9,6 @@ #define BEAST_HTTP_TEST_MESSAGE_FUZZ_HPP #include -#include #include #include #include diff --git a/test/http/message_parser.cpp b/test/http/message_parser.cpp new file mode 100644 index 0000000000..de3842d9aa --- /dev/null +++ b/test/http/message_parser.cpp @@ -0,0 +1,244 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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 message_parser_test + : public beast::unit_test::suite + , public beast::test::enable_yield_to +{ +public: + template + void + testMatrix(std::string const& s, Pred const& pred) + { + beast::test::string_istream ss{get_io_service(), s}; + error_code ec; + #if 0 + streambuf dynabuf; + #else + flat_streambuf dynabuf{1024}; + #endif + message m; + read(ss, dynabuf, m, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + pred(m); + } + + void + testRead() + { + testMatrix( + "HTTP/1.0 200 OK\r\n" + "Server: test\r\n" + "\r\n" + "*******", + [&](message const& m) + { + BEAST_EXPECTS(m.body == "*******", + "body='" + m.body + "'"); + } + ); + testMatrix( + "HTTP/1.0 200 OK\r\n" + "Server: test\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", + [&](message const& m) + { + BEAST_EXPECT(m.body == "*****--"); + } + ); + testMatrix( + "HTTP/1.0 200 OK\r\n" + "Server: test\r\n" + "Content-Length: 5\r\n" + "\r\n" + "*****", + [&](message const& m) + { + BEAST_EXPECT(m.body == "*****"); + } + ); + testMatrix( + "GET / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "\r\n", + [&](message const& m) + { + } + ); + testMatrix( + "GET / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "X: \t x \t \r\n" + "\r\n", + [&](message const& m) + { + BEAST_EXPECT(m.fields["X"] == "x"); + } + ); + } + + void + testParse() + { + using boost::asio::buffer; + { + error_code ec; + beast::test::string_istream is{ + get_io_service(), + "GET / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*"}; + flat_streambuf sb{1024}; + message_parser p; + read(is, sb, p, ec); + auto const& m = p.get(); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_complete()); + 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 == "*"); + } +#if 0 + { + // test partial parsing of final chunk + // parse through the chunk body + beast::test::string_istream is{ + get_io_service(), ""}; + streambuf sb; + sb << + "PUT / HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "1\r\n" + "*"; + error_code ec; + message_parser p; + read(is, sb, p, ec); + BEAST_EXPECT(sb.size() == 0); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(!p.is_complete()); + BEAST_EXPECT(p.get().body == "*"); + sb << "\r\n0;d;e=3;f=\"4\"\r\n" + "Expires: never\r\n" + "MD5-Fingerprint: -\r\n"; + // incomplete parse, missing the final crlf + BEAST_EXPECT(p.write(sb.data(), ec) == 0); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(!p.is_complete()); + sb << "\r\n"; // final crlf to end message + BEAST_EXPECT(p.write(sb.data(), ec) == sb.size()); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_complete()); + } + { + error_code ec; + message_parser 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); + auto const& m = p.get(); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_complete()); + 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; + message_parser p; + std::string const s = + "HTTP/1.1 200 Connection Established\r\n" + "Proxy-Agent: Zscaler/5.1\r\n" + "\r\n"; + p.skip_body(); + p.write(buffer(s), ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_complete()); + } +#endif + } + + void + testExpect100Continue() + { + test::string_istream ss{ios_, + "POST / HTTP/1.1\r\n" + "Expect: 100-continue\r\n" + "Content-Length: 5\r\n" + "\r\n" + "*****"}; + streambuf sb; + error_code ec; + header_parser p0; + auto const bytes_used = + read_some(ss, sb, p0, ec); + sb.consume(bytes_used); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p0.state() != parse_state::header); + BEAST_EXPECT(! p0.is_complete()); + message_parser p1{std::move(p0)}; + read(ss, sb, p1, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p1.get().body == "*****"); + } + + void + run() override + { + testRead(); + testParse(); + testExpect100Continue(); + } +}; + +BEAST_DEFINE_TESTSUITE(message_parser,http,beast); + +} // http +} // beast + diff --git a/test/http/nodejs_parser.hpp b/test/http/nodejs_parser.hpp index 83ee3665ad..f2fbe62d09 100644 --- a/test/http/nodejs_parser.hpp +++ b/test/http/nodejs_parser.hpp @@ -740,11 +740,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 +747,6 @@ class nodejs_parser nodejs_parser() : http::nodejs_basic_parser(isRequest) - , r_(m_) { } @@ -763,12 +757,6 @@ class nodejs_parser return started_; } - message_type - release() - { - return std::move(m_); - } - private: friend class http::nodejs_basic_parser; @@ -781,7 +769,6 @@ class nodejs_parser void on_field(std::string const& field, std::string const& value) { - m_.fields.insert(field, value); } void @@ -798,9 +785,6 @@ class nodejs_parser int major, int minor, bool /*keep_alive*/, bool /*upgrade*/, std::true_type) { - m_.method = detail::method_to_string(method); - m_.url = url; - m_.version = major * 10 + minor; return true; } @@ -819,7 +803,7 @@ class nodejs_parser return on_request(method, url, major, minor, keep_alive, upgrade, std::integral_constant< - bool, message_type::is_request>{}); + bool, isRequest>{}); } bool @@ -828,10 +812,6 @@ class nodejs_parser 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 +828,13 @@ class nodejs_parser { 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) { - r_.write(data, size, ec); } void diff --git a/test/http/parse.cpp b/test/http/parse.cpp deleted file mode 100644 index b15e9e4362..0000000000 --- a/test/http/parse.cpp +++ /dev/null @@ -1,9 +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 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_bench.cpp b/test/http/parser_bench.cpp index 768d267d70..5015738e8c 100644 --- a/test/http/parser_bench.cpp +++ b/test/http/parser_bench.cpp @@ -9,6 +9,7 @@ #include "message_fuzz.hpp" #include +#include #include #include #include @@ -64,9 +65,39 @@ class parser_bench_test : public beast::unit_test::suite 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.write(cb, ec); + if(ec) + return 0; + if(n == 0) + break; + cb.consume(n); + used += n; + if(parser.is_complete()) + break; + if(buffer_size(cb) == 0) + break; + } + return used; + } + template void - testParser(std::size_t repeat, corpus const& v) + testParser1(std::size_t repeat, corpus const& v) { while(repeat--) for(auto const& sb : v) @@ -79,6 +110,21 @@ class parser_bench_test : public beast::unit_test::suite } } + template + void + testParser2(std::size_t repeat, corpus const& v) + { + while(repeat--) + for(auto const& sb : v) + { + Parser p; + error_code ec; + feed(sb.data(), p, 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) @@ -98,21 +144,92 @@ class parser_bench_test : public beast::unit_test::suite } template - struct null_parser : basic_parser_v1> + struct null_parser : + basic_parser> { }; + template + struct bench_parser : basic_parser< + isRequest, false, bench_parser> + { + using mutable_buffers_type = + boost::asio::mutable_buffers_1; + + void + on_request(boost::string_ref const&, + boost::string_ref const&, + int, error_code&) + { + } + + void + on_response(int, + boost::string_ref const&, + int, error_code&) + { + } + + void + on_field(boost::string_ref const&, + boost::string_ref const&, + error_code&) + { + } + + void + on_header(error_code& ec) + { + } + + void + on_body(error_code& ec) + { + } + + void + on_body(std::uint64_t content_length, + error_code& ec) + { + } + + void + on_data(boost::string_ref const&, + error_code& ec) + { + } + + void + on_chunk(std::uint64_t, + boost::string_ref const&, + error_code&) + { + } + + void + on_body(boost::string_ref const&, + error_code&) + { + } + + void + on_complete(error_code&) + { + } + }; + void testSpeed() { static std::size_t constexpr Trials = 3; - static std::size_t constexpr Repeat = 50; + static std::size_t constexpr Repeat = 500; log << "sizeof(request parser) == " << - sizeof(basic_parser_v1>) << '\n'; + sizeof(null_parser) << '\n'; log << "sizeof(response parser) == " << - sizeof(basic_parser_v1>)<< '\n'; + sizeof(null_parser)<< '\n'; testcase << "Parser speed test, " << ((Repeat * size_ + 512) / 1024) << "KB in " << @@ -121,20 +238,20 @@ class parser_bench_test : public beast::unit_test::suite timedTest(Trials, "nodejs_parser", [&] { - testParser>( Repeat, creq_); - testParser>( Repeat, cres_); }); - timedTest(Trials, "http::basic_parser_v1", + timedTest(Trials, "http::basic_parser", [&] { - testParser>( + testParser2 >( Repeat, creq_); - testParser>( Repeat, cres_); }); 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..3e80b8ebd7 100644 --- a/test/http/read.cpp +++ b/test/http/read.cpp @@ -8,10 +8,12 @@ // Test that header file is self-contained. #include -#include "fail_parser.hpp" +#include "test_parser.hpp" #include +#include #include +#include #include #include #include @@ -27,62 +29,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; @@ -97,9 +46,9 @@ class read_test test::fail_counter fc(n); test::fail_stream< test::string_istream> fs{fc, ios_, ""}; - fail_parser p(fc); + test_parser p(fc); error_code ec; - parse(fs, sb, p, ec); + read(fs, sb, p, ec); if(! ec) break; } @@ -113,9 +62,9 @@ class read_test test::fail_counter fc(n); test::fail_stream fs{ fc, ios_, std::string{s + pre, len - pre}}; - fail_parser p(fc); + test_parser p(fc); error_code ec; - parse(fs, sb, p, ec); + read(fs, sb, p, ec); if(! ec) break; } @@ -128,9 +77,9 @@ class read_test test::fail_counter fc(n); test::fail_stream< test::string_istream> fs{fc, ios_, ""}; - fail_parser p(fc); + test_parser p(fc); error_code ec; - async_parse(fs, sb, p, do_yield[ec]); + async_read(fs, sb, p, do_yield[ec]); if(! ec) break; } @@ -144,23 +93,9 @@ class read_test test::fail_counter fc(n); test::fail_stream fs{ fc, ios_, std::string{s + pre, len - pre}}; - fail_parser p(fc); + test_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); + async_read(fs, sb, p, do_yield[ec]); if(! ec) break; } @@ -173,8 +108,8 @@ class read_test { streambuf sb; test::string_istream ss(ios_, "GET / X"); - parser_v1 p; - parse(ss, sb, p); + message_parser p; + read(ss, sb, p); fail(); } catch(std::exception const&) @@ -245,52 +180,6 @@ class read_test 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; @@ -355,22 +244,23 @@ class read_test BEAST_EXPECT(n < limit); } - void testEof(yield_context do_yield) + void + testEof(yield_context do_yield) { { streambuf sb; test::string_istream ss(ios_, ""); - parser_v1 p; + message_parser p; error_code ec; - parse(ss, sb, p, ec); + read(ss, sb, p, ec); BEAST_EXPECT(ec == boost::asio::error::eof); } { streambuf sb; test::string_istream ss(ios_, ""); - parser_v1 p; + message_parser p; error_code ec; - async_parse(ss, sb, p, do_yield[ec]); + async_read(ss, sb, p, do_yield[ec]); BEAST_EXPECT(ec == boost::asio::error::eof); } } @@ -424,12 +314,12 @@ class read_test } } - void run() override + void + run() override { testThrow(); yield_to(&read_test::testFailures, this); - yield_to(&read_test::testReadHeaders, this); yield_to(&read_test::testRead, this); yield_to(&read_test::testEof, this); diff --git a/test/http/rfc7230.cpp b/test/http/rfc7230.cpp index 1e486bfa5f..a02b2cc723 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 { @@ -135,9 +136,9 @@ class rfc7230_test : public beast::unit_test::suite 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 @@ class rfc7230_test : public beast::unit_test::suite cs("x y", "x"); } + template + static + std::vector + to_vector(boost::string_ref const& 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(boost::string_ref const& in, + std::vector const& v) + { + BEAST_EXPECT(to_vector(in) == v); + } + + template + void + good(boost::string_ref const& in) + { + BEAST_EXPECT(validate_list( + detail::basic_parsed_list{in})); + } + + template + void + good(boost::string_ref const& in, + std::vector const& v) + { + BEAST_EXPECT(validate_list( + detail::basic_parsed_list{in})); + validate(in, v); + } + + template + void + bad(boost::string_ref const& 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/streambuf_body.cpp b/test/http/streambuf_body.cpp index 2b5ed5f7a7..9e796c6b58 100644 --- a/test/http/streambuf_body.cpp +++ b/test/http/streambuf_body.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include #include #include @@ -34,11 +34,12 @@ class streambuf_body_test : public beast::unit_test::suite "\r\n" "xyz"; test::string_istream ss(ios_, s); - parser_v1 p; + message_parser p; streambuf sb; - parse(ss, sb, p); - BEAST_EXPECT(to_string(p.get().body.data()) == "xyz"); - BEAST_EXPECT(boost::lexical_cast(p.get()) == s); + read(ss, sb, p); + auto const& m = p.get(); + BEAST_EXPECT(to_string(m.body.data()) == "xyz"); + BEAST_EXPECT(boost::lexical_cast(m) == s); } }; diff --git a/test/http/test_parser.hpp b/test/http/test_parser.hpp new file mode 100644 index 0000000000..7d2611c20b --- /dev/null +++ b/test/http/test_parser.hpp @@ -0,0 +1,147 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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 + +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; + bool got_on_begin = false; + bool got_on_field = false; + bool got_on_header = false; + bool got_on_body = false; + bool got_content_length = false; + bool got_on_prepare = false; + bool got_on_commit = false; + bool got_on_chunk = false; + bool got_on_complete = false; + + test_parser() = default; + + explicit + test_parser(test::fail_counter& fc) + : fc_(&fc) + { + } + + void + on_request( + boost::string_ref const& method_, + boost::string_ref const& path_, + int version_, error_code& ec) + { + method = std::string( + method_.data(), method_.size()); + path = std::string( + path_.data(), path_.size()); + version = version_; + got_on_begin = true; + if(fc_) + fc_->fail(ec); + } + + void + on_response(int status_, + boost::string_ref const& reason_, + int version_, error_code& ec) + { + status = status_; + reason = std::string( + reason_.data(), reason_.size()); + version = version_; + got_on_begin = true; + if(fc_) + fc_->fail(ec); + } + + void + on_field(boost::string_ref const&, + boost::string_ref const&, + error_code& ec) + { + got_on_field = true; + if(fc_) + fc_->fail(ec); + } + + void + on_header(error_code& ec) + { + got_on_header = true; + if(fc_) + fc_->fail(ec); + } + + void + on_body(error_code& ec) + { + got_on_body = true; + if(fc_) + fc_->fail(ec); + } + + void + on_body(std::uint64_t content_length, + error_code& ec) + { + got_on_body = true; + got_content_length = true; + if(fc_) + fc_->fail(ec); + } + + void + on_data(boost::string_ref const& s, + error_code& ec) + { + body.append(s.data(), s.size()); + } + + void + on_chunk(std::uint64_t, + boost::string_ref const&, + error_code& ec) + { + got_on_chunk = true; + if(fc_) + fc_->fail(ec); + } + + void + on_complete(error_code& ec) + { + got_on_complete = true; + if(fc_) + fc_->fail(ec); + } +}; + +} // http +} // beast + +#endif diff --git a/test/http/write.cpp b/test/http/write.cpp index 79fe11ab55..2ffe7a155e 100644 --- a/test/http/write.cpp +++ b/test/http/write.cpp @@ -10,7 +10,6 @@ #include #include -#include #include #include #include @@ -510,7 +509,7 @@ class write_test } // upgrade HTTP/1.1 { - message m; + message m; m.method = "GET"; m.url = "/"; m.version = 11; diff --git a/test/websocket/CMakeLists.txt b/test/websocket/CMakeLists.txt index 5d2856edb3..e73a784370 100644 --- a/test/websocket/CMakeLists.txt +++ b/test/websocket/CMakeLists.txt @@ -10,14 +10,7 @@ add_executable (websocket-tests ../../extras/beast/unit_test/main.cpp websocket_async_echo_server.hpp websocket_sync_echo_server.hpp - error.cpp - option.cpp - rfc6455.cpp stream.cpp - teardown.cpp - frame.cpp - mask.cpp - utf8_checker.cpp ) if (NOT WIN32) diff --git a/test/websocket/frame.cpp b/test/websocket/frame.cpp index 13d2c51039..550a49a5fc 100644 --- a/test/websocket/frame.cpp +++ b/test/websocket/frame.cpp @@ -35,20 +35,20 @@ class frame_test : public beast::unit_test::suite 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 @@ -77,7 +77,7 @@ class frame_test : public beast::unit_test::suite { fh_streambuf sb; write(sb, fh); - close_code::value code; + close_code code; stream_base stream; stream.open(role); detail::frame_header fh1; @@ -129,7 +129,7 @@ class frame_test : public beast::unit_test::suite { fh_streambuf sb; write(sb, fh); - close_code::value code; + close_code code; stream_base stream; stream.open(role); detail::frame_header fh1; @@ -197,7 +197,7 @@ class frame_test : public beast::unit_test::suite sb.commit(buffer_copy(sb.prepare(v.size()), buffer(v))); stream_base stream; stream.open(role); - close_code::value code; + close_code code; detail::frame_header fh; auto const n = stream.read_fh1(fh, sb, code); diff --git a/test/websocket/rfc6455.cpp b/test/websocket/rfc6455.cpp index c34daf312f..080b5f4387 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::request_header req; + req.version = 10; + BEAST_EXPECT(! is_upgrade(req)); + req.version = 11; + req.method = "POST"; + req.url = "/"; + BEAST_EXPECT(! is_upgrade(req)); + req.method = "GET"; + req.fields.insert("Connection", "upgrade"); + BEAST_EXPECT(! is_upgrade(req)); + req.fields.insert("Upgrade", "websocket"); + BEAST_EXPECT(! is_upgrade(req)); + req.fields.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..aae8ea2df1 100644 --- a/test/websocket/stream.cpp +++ b/test/websocket/stream.cpp @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include #include #include @@ -109,30 +111,428 @@ class stream_test return false; } - struct test_decorator + struct SyncClient + { + template + void + accept(stream& ws) const + { + ws.accept(); + } + + template + void + accept(stream& ws, + Buffers const& buffers) const + { + ws.accept(buffers); + } + + template + void + accept(stream& ws, + http::header const& req) const + { + 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 + void + 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, + boost::string_ref const& uri, + boost::string_ref const& path) const + { + ws.handshake(uri, path); + } + + template + void + handshake(stream& ws, + response_type& res, + boost::string_ref const& uri, + boost::string_ref const& path) const + { + ws.handshake(res, uri, path); + } + + template + void + handshake_ex(stream& ws, + boost::string_ref const& uri, + boost::string_ref const& path, + Decorator const& d) const + { + ws.handshake_ex(uri, path, d); + } + + template + void + handshake_ex(stream& ws, + response_type& res, + boost::string_ref const& uri, + boost::string_ref const& 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, + 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 { - int& what; + 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 + void + 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 + void + 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, + 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 + handshake(stream& ws, + response_type& res, + boost::string_ref const& uri, + boost::string_ref const& 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, + boost::string_ref const& uri, + boost::string_ref const& 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, + boost::string_ref const& uri, + boost::string_ref const& 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}; + } - test_decorator(test_decorator const&) = default; + 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}; + } - test_decorator(int& what_) - : what(what_) + template< + class NextLayer, class ConstBufferSequence> + void + write(stream& ws, + ConstBufferSequence const& buffers) const { - what = 0; + error_code ec; + ws.async_write(buffers, yield_[ec]); + if(ec) + throw system_error{ec}; } - template + template< + class NextLayer, class ConstBufferSequence> void - operator()(http::header&) const + write_frame(stream& ws, bool fin, + ConstBufferSequence const& buffers) const { - what |= 1; + error_code ec; + ws.async_write_frame(fin, buffers, yield_[ec]); + if(ec) + throw system_error{ec}; } - template + template< + class NextLayer, class ConstBufferSequence> void - operator()(http::header&) const + write_raw(stream& ws, + ConstBufferSequence const& buffers) const { - what |= 2; + error_code ec; + boost::asio::async_write( + ws.next_layer(), buffers, yield_[ec]); + if(ec) + throw system_error{ec}; } }; @@ -157,84 +557,432 @@ class stream_test } try { - message_type{opcode::close}; - fail(); + message_type{opcode::close}; + fail(); + } + catch(std::exception const&) + { + pass(); + } + } + + //-------------------------------------------------------------------------- + + class res_decorator + { + bool& b_; + + public: + res_decorator(res_decorator const&) = default; + + explicit + res_decorator(bool& b) + : b_(b) + { + } + + void + operator()(response_type&) const + { + 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 + { + // 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 = "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{fc, ios_}; + c.accept(ws, req); + } + { + request_type 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{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 = "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{fc, ios_}; + c.accept(ws, req, + cbuf(0x88, 0x82, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x17)); + try + { + opcode op; + streambuf sb; + c.read(ws, op, sb); + fail("success", __FILE__, __LINE__); + } + catch(system_error const& e) + { + if(e.code() != websocket::error::closed) + throw; + } + } + { + request_type 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{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 + { + opcode op; + streambuf sb; + c.read(ws, op, sb); + 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 = "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{fc, ios_, + "\x88\x82\xff\xff\xff\xff\xfc\x17"}; + c.accept(ws, req); + try + { + opcode op; + streambuf sb; + c.read(ws, op, sb); + 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 = "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{fc, ios_, + "xff\xff\xfc\x17"}; + c.accept(ws, req, + cbuf(0x88, 0x82, 0xff, 0xff)); + try + { + opcode op; + streambuf sb; + c.read(ws, op, sb); + 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&) + { + 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) + { } - catch(std::exception const&) + + void + operator()(request_type&) const { - pass(); + b_ = true; } - } + }; - void testAccept() + template + void + testHandshake(endpoint_type const& ep, Client const& c) { + static std::size_t constexpr limit = 200; + std::size_t n; + for(n = 0; n < limit; ++n) { - static std::size_t constexpr limit = 100; - std::size_t n; - for(n = 0; n < limit; ++n) + test::fail_counter fc{n}; + try { - // 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 + // handshake { - ws.accept(req); - break; + stream> ws{fc, ios_}; + ws.next_layer().next_layer().connect(ep); + c.handshake(ws, "localhost", "/"); } - catch(system_error const&) + // 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); } - } - BEAST_EXPECT(n < limit); - } - { - // 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" - ); - try - { - ws.accept(); - pass(); } catch(system_error const&) { - fail(); + continue; } + break; } - { - // invalid - stream ws(ios_, - "GET / HTTP/1.0\r\n" - "\r\n" - ); - try - { - ws.accept(); - fail(); - } - catch(system_error const&) + BEAST_EXPECT(n < limit); + } + + void + testHandshake() + { + error_code ec; + ::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) { - pass(); - } - } + testHandshake(ep, AsyncClient{yield}); + }); } + //-------------------------------------------------------------------------- + void testBadHandshakes() { auto const check = @@ -423,24 +1171,6 @@ class stream_test ); } - 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, yield_context do_yield) { @@ -863,185 +1593,6 @@ class stream_test } } - 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 { }; @@ -1302,15 +1853,17 @@ class stream_test auto const any = endpoint_type{ address_type::from_string("127.0.0.1"), 0}; + permessage_deflate pmd; + pmd.client_enable = false; + pmd.server_enable = false; + +#if 0 testOptions(); testAccept(); + testHandshake(); testBadHandshakes(); testBadResponses(); - permessage_deflate pmd; - pmd.client_enable = false; - pmd.server_enable = false; - { error_code ec; ::websocket::sync_echo_server server{nullptr}; @@ -1318,7 +1871,6 @@ class stream_test server.open(any, ec); BEAST_EXPECTS(! ec, ec.message()); auto const ep = server.local_endpoint(); - testDecorator(ep); //testInvokable1(ep); testInvokable2(ep); testInvokable3(ep); @@ -1336,6 +1888,7 @@ class stream_test auto const ep = server.local_endpoint(); testAsyncWriteFrame(ep); } +#endif auto const doClientTests = [this, any](permessage_deflate const& pmd) @@ -1372,21 +1925,27 @@ class stream_test } }; +#if 0 pmd.client_enable = false; pmd.server_enable = false; doClientTests(pmd); +#endif +#if 1 pmd.client_enable = true; pmd.server_enable = true; pmd.client_max_window_bits = 10; pmd.client_no_context_takeover = false; doClientTests(pmd); +#endif +#if 0 pmd.client_enable = true; pmd.server_enable = true; pmd.client_max_window_bits = 10; pmd.client_no_context_takeover = true; doClientTests(pmd); +#endif } }; diff --git a/test/websocket/websocket_async_echo_server.hpp b/test/websocket/websocket_async_echo_server.hpp index 4932e35e5f..ebbada11b9 100644 --- a/test/websocket/websocket_async_echo_server.hpp +++ b/test/websocket/websocket_async_echo_server.hpp @@ -38,25 +38,6 @@ class async_echo_server 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 +140,6 @@ class async_echo_server , 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( @@ -282,7 +261,13 @@ class async_echo_server void run() { auto& d = *d_; - d.ws.async_accept(std::move(*this)); + d.ws.async_accept_ex( + [](beast::websocket::response_type& res) + { + res.fields.insert( + "Server", "async_echo_server"); + }, + std::move(*this)); } template diff --git a/test/websocket/websocket_sync_echo_server.hpp b/test/websocket/websocket_sync_echo_server.hpp index 8f1dbf0757..3c3ab4de2f 100644 --- a/test/websocket/websocket_sync_echo_server.hpp +++ b/test/websocket/websocket_sync_echo_server.hpp @@ -38,25 +38,6 @@ class sync_echo_server 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 +132,6 @@ class sync_echo_server , sock_(ios_) , acceptor_(ios_) { - opts_.set_option( - beast::websocket::decorate(identity{})); } /** Destructor. @@ -312,7 +291,13 @@ class sync_echo_server 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.fields.insert( + "Server", "sync_echo_server"); + }, + ec); if(ec) { fail("accept", ec, id, ep);