diff --git a/appveyor.yml b/appveyor.yml index 3a3b3ee2..93614033 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -57,6 +57,7 @@ install: - git submodule update --init libs/lambda - git submodule update --init libs/lexical_cast - git submodule update --init libs/locale + - git submodule update --init libs/logic - git submodule update --init libs/math - git submodule update --init libs/move - git submodule update --init libs/mpl diff --git a/doc/0_main.qbk b/doc/0_main.qbk index aa5ec904..3b4b3356 100644 --- a/doc/0_main.qbk +++ b/doc/0_main.qbk @@ -71,9 +71,11 @@ [def __static_buffer__ [link beast.ref.static_buffer `static_buffer`]] [def __static_buffer_n__ [link beast.ref.static_buffer_n `static_buffer_n`]] +[import ../examples/file_body.hpp] [import ../examples/http_example.cpp] [import ../examples/websocket_example.cpp] [import ../examples/echo_op.cpp] +[import ../examples/doc_core_samples.hpp] [import ../examples/doc_http_samples.hpp] [import ../test/core/doc_snippets.cpp] [import ../test/http/doc_snippets.cpp] @@ -83,11 +85,12 @@ [include 1_overview.qbk] [include 2_examples.qbk] [include 3_0_core.qbk] -[include 4_00_http.qbk] -[include 5_http_examples.qbk] -[include 6_0_websocket.qbk] -[include 7_concepts.qbk] -[include 8_0_design.qbk] +[include 4_0_network.qbk] +[include 5_00_http.qbk] +[include 6_0_http_examples.qbk] +[include 7_0_websocket.qbk] +[include 8_concepts.qbk] +[include 9_0_design.qbk] [section:quickref Reference] [xinclude quickref.xml] diff --git a/doc/1_overview.qbk b/doc/1_overview.qbk index 3ba5a6d9..c193cde9 100644 --- a/doc/1_overview.qbk +++ b/doc/1_overview.qbk @@ -8,13 +8,13 @@ [section:overview Introduction] [important - Beast is a cross-platform, header-only C++11 library for low-level - [*HTTP/1 and WebSocket protocol] programming - using the consistent asynchronous networking model of __Asio__. - Beast is not an HTTP client or HTTP server, but it can be used to - build those things. It is intended to be a foundation for writing - other interoperable libraries by providing HTTP vocabulary types - and algorithms. The provided examples show how clients and servers + Beast is a cross-platform, header-only C++11 library for + [*low-level HTTP/1, WebSocket, and network protocol] programming + using the consistent asynchronous model of __Asio__. Beast is + not an HTTP client or HTTP server, but it can be used to build + those things. It is intended to be a foundation for writing other + interoperable libraries by providing HTTP vocabulary types and + algorithms. The provided examples show how clients and servers might be built. ] diff --git a/doc/3_0_core.qbk b/doc/3_0_core.qbk index f5b9fb36..ac2528b0 100644 --- a/doc/3_0_core.qbk +++ b/doc/3_0_core.qbk @@ -5,7 +5,7 @@ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] -[section:core Library Basics] +[section:core Using Networking] A goal of the library is expose implementation primitives in order that users may build their own library-like components. These primitives include @@ -26,6 +26,5 @@ lists these facilities by group, with descriptions. [include 3_2_streams.qbk] [include 3_3_buffers.qbk] [include 3_4_async.qbk] -[include 3_5_op_tutorial.qbk] [endsect] diff --git a/doc/3_4_async.qbk b/doc/3_4_async.qbk index 4b489363..e86440ac 100644 --- a/doc/3_4_async.qbk +++ b/doc/3_4_async.qbk @@ -15,7 +15,7 @@ customization of how the result of the asynchronous operation is conveyed to callers. __Asio__ allows the special completion tokens __use_future__ and objects of type __yield_context__ to allow callers to specify the use of futures and coroutines respectively. This system, where the return value and method of -indicating completion may be customize at the call site of the asynchronous +indicating completion may be customized at the call site of the asynchronous initiation function, is known as the ['Extensible Asynchronous Model] described in __N3747__, and built-in to __N4588__. diff --git a/doc/4_0_network.qbk b/doc/4_0_network.qbk new file mode 100644 index 00000000..8274c964 --- /dev/null +++ b/doc/4_0_network.qbk @@ -0,0 +1,17 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:network Network Examples] + +These examples in this section are working functions that may be found +in the examples directory. They demonstrate the usage of the library +for a variety of scenarios. + +[include 4_1_detect_tls.qbk] +[include 4_2_echo.qbk] + +[endsect] diff --git a/doc/4_1_detect_tls.qbk b/doc/4_1_detect_tls.qbk new file mode 100644 index 00000000..1b775685 --- /dev/null +++ b/doc/4_1_detect_tls.qbk @@ -0,0 +1,64 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section:detect_tls TLS/SSL Detector Composed Operation] + +In this example we will build a simple function to detect the presence +of the TLS handshake given an input buffer sequence. Then we build on +the example by adding synchronous stream algorithms. Finally, we +implemement an asynchronous detection function using a composed operation. +This SSL detector may be used to allow a server to accept both TLS and +unencrypted connections at the same port. + +Here is the declaration for a function to detect the SSL client handshake. +The input to the function is simply a buffer sequence, no stream. This +allows the detection algorithm to be used elsewhere. + +[core_sample_detect_tls_1] + +The implementation checks the buffer for the presence of the SSL +Handshake message octet sequence and returns an apporopriate value: + +[core_sample_detect_tls_2] + +Now we define a stream operation. We start with the simple, +synchronous version which takes the stream and buffer as input: + +[core_sample_detect_tls_3] + +The synchronous algorithm is the model for building the asynchronous +operation which has more boilerplate. First, we declare the asynchronous +initiation function: + +[core_sample_detect_tls_4] + +The implementation of the initiation function is straightforward +and contains mostly boilerplate. It is to construct the return +type customization helper to obtain the actual handler, and +then create the composed operation and launch it. The actual +code for interacting with the stream is in the composed operation, +which is written as a separate class. + +[core_sample_detect_tls_5] + +Now we will declare our composed operation. There is a considerable +amount of necessary boilerplate to get this right, but the result +is worth the effort. + +[core_sample_detect_tls_6] + +The boilerplate is all done, and now we need to implemnt the function +call operator that turns this composed operation a completion handler +with the signature `void(error_code, std::size_t)` which is exactly +the signature needed when performing asynchronous reads. This function +is a transformation of the synchronous version of `detect_ssl` above, +but with the inversion of flow that characterizes code written in the +callback style: + +[core_sample_detect_tls_7] + +[endsect] diff --git a/doc/3_5_op_tutorial.qbk b/doc/4_2_echo.qbk similarity index 92% rename from doc/3_5_op_tutorial.qbk rename to doc/4_2_echo.qbk index 0fcde8d6..f9d51a95 100644 --- a/doc/3_5_op_tutorial.qbk +++ b/doc/4_2_echo.qbk @@ -5,12 +5,13 @@ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] -[section:op_tutorial Writing a Composed Operation] +[section:echo Echo Composed Operation] -To illustrate the usage of the asynchronous helpers in the core section of -this library, we will develop a simple asynchronous composed operation called +Here we developed a more advanced asynchronous composed operation called [*echo]. This operation will read up to the first newline on a stream, and -then write the same line including the newline back on the stream. +then write the same line including the newline back on the stream. The +implementation performs both reading and writing, and has a +non-trivially-copyable state. First we define the input parameters and results, then declare our initiation function. For our echo operation the only inputs are the stream and the diff --git a/doc/4_00_http.qbk b/doc/5_00_http.qbk similarity index 90% rename from doc/4_00_http.qbk rename to doc/5_00_http.qbk index 5d604db1..48b07258 100644 --- a/doc/4_00_http.qbk +++ b/doc/5_00_http.qbk @@ -78,14 +78,14 @@ format using __Asio__. Specifically, the library provides: [http_snippet_1] ] -[include 4_01_primer.qbk] -[include 4_02_message.qbk] -[include 4_03_streams.qbk] -[include 4_04_serializer_streams.qbk] -[include 4_05_parser_streams.qbk] -[include 4_06_serializer_buffers.qbk] -[include 4_07_parser_buffers.qbk] -[include 4_08_custom_parsers.qbk] -[include 4_09_custom_body.qbk] +[include 5_01_primer.qbk] +[include 5_02_message.qbk] +[include 5_03_streams.qbk] +[include 5_04_serializer_streams.qbk] +[include 5_05_parser_streams.qbk] +[include 5_06_serializer_buffers.qbk] +[include 5_07_parser_buffers.qbk] +[include 5_08_custom_parsers.qbk] +[include 5_09_custom_body.qbk] [endsect] diff --git a/doc/4_01_primer.qbk b/doc/5_01_primer.qbk similarity index 100% rename from doc/4_01_primer.qbk rename to doc/5_01_primer.qbk diff --git a/doc/4_02_message.qbk b/doc/5_02_message.qbk similarity index 100% rename from doc/4_02_message.qbk rename to doc/5_02_message.qbk diff --git a/doc/4_03_streams.qbk b/doc/5_03_streams.qbk similarity index 100% rename from doc/4_03_streams.qbk rename to doc/5_03_streams.qbk diff --git a/doc/4_04_serializer_streams.qbk b/doc/5_04_serializer_streams.qbk similarity index 100% rename from doc/4_04_serializer_streams.qbk rename to doc/5_04_serializer_streams.qbk diff --git a/doc/4_05_parser_streams.qbk b/doc/5_05_parser_streams.qbk similarity index 100% rename from doc/4_05_parser_streams.qbk rename to doc/5_05_parser_streams.qbk diff --git a/doc/4_06_serializer_buffers.qbk b/doc/5_06_serializer_buffers.qbk similarity index 100% rename from doc/4_06_serializer_buffers.qbk rename to doc/5_06_serializer_buffers.qbk diff --git a/doc/4_07_parser_buffers.qbk b/doc/5_07_parser_buffers.qbk similarity index 100% rename from doc/4_07_parser_buffers.qbk rename to doc/5_07_parser_buffers.qbk diff --git a/doc/4_08_custom_parsers.qbk b/doc/5_08_custom_parsers.qbk similarity index 100% rename from doc/4_08_custom_parsers.qbk rename to doc/5_08_custom_parsers.qbk diff --git a/doc/4_09_custom_body.qbk b/doc/5_09_custom_body.qbk similarity index 100% rename from doc/4_09_custom_body.qbk rename to doc/5_09_custom_body.qbk diff --git a/doc/5_http_examples.qbk b/doc/6_0_http_examples.qbk similarity index 99% rename from doc/5_http_examples.qbk rename to doc/6_0_http_examples.qbk index 6eb5310e..2bd133f3 100644 --- a/doc/5_http_examples.qbk +++ b/doc/6_0_http_examples.qbk @@ -11,6 +11,28 @@ These examples in this section are working functions that may be found in the examples directory. They demonstrate the usage of the library for a variety of scenarios. + + +[section Change Body Type] + +Sophisticated servers may wish to defer the choice of the Body template type +until after the header is available. Then, a body type may be chosen +depending on the header contents. For example, depending on the verb, +target path, or target query parameters. To accomplish this, a parser +is declared to read in the header only, using a trivial body type such as +[link beast.ref.http__empty_body `empty_body`]. Then, a new parser is constructed +from this existing parser where the body type is conditionally determined +by information from the header or elsewhere. + +This example illustrates how a server may make the commitment of a body +type depending on the method verb: + +[http_sample_defer_body] + +[endsect] + + + [section Expect 100-continue (Client)] The Expect field with the value "100-continue" in a request is special. It @@ -48,6 +70,10 @@ synchronous version of this server action looks like this: +[include 6_1_file_body.qbk] + + + [section HEAD request (Client)] The @@ -75,21 +101,21 @@ if the method was GET, except that the body is omitted. -[section Write To std::ostream] +[section HTTP Relay] -The standard library provides the type `std::ostream` for performing high -level write operations on character streams. The variable `std::cout` is -based on this output stream. In this example, we build a stream operation -which serializes an HTTP message to a `std::ostream`: +An HTTP proxy acts as a relay between client and server. The proxy reads a +request from the client and sends it to the server, possibly adjusting some +of the headers and representation of the body along the way. Then, the +proxy reads a response from the server and sends it back to the client, +also with the possibility of changing the headers and body representation. -[http_sample_write_ostream] +The example that follows implements a synchronous HTTP relay. It uses a +fixed size buffer, to avoid reading in the entire body so that the upstream +connection sees a header without unnecessary latency. This example brings +together all of the concepts discussed so far, it uses both a __serializer__ +and a __parser__ to achieve its goal: -[tip - Serializing to a `std::ostream` could be implemented using an alternate - strategy: adapt the `std::ostream` interface to a __SyncWriteStream__. - This lets all the library's existing algorithms work on `std::ostream`. - We leave this as an exercise for the reader. -] +[http_sample_relay] [endsect] @@ -138,43 +164,21 @@ HTTP response. The output of the process is sent as it becomes available: -[section Defer Body Type] +[section Write To std::ostream] -Sophisticated servers may wish to defer the choice of the Body template type -until after the header is available. Then, a body type may be chosen -depending on the header contents. For example, depending on the verb, -target path, or target query parameters. To accomplish this, a parser -is declared to read in the header only, using a trivial body type such as -[link beast.ref.http__empty_body `empty_body`]. Then, a new parser is constructed -from this existing parser where the body type is conditionally determined -by information from the header or elsewhere. +The standard library provides the type `std::ostream` for performing high +level write operations on character streams. The variable `std::cout` is +based on this output stream. In this example, we build a stream operation +which serializes an HTTP message to a `std::ostream`: -This example illustrates how a server may make the commitment of a body -type depending on the method verb: +[http_sample_write_ostream] -[http_sample_defer_body] - -[endsect] - - - - - -[section HTTP Relay] - -An HTTP proxy acts as a relay between client and server. The proxy reads a -request from the client and sends it to the server, possibly adjusting some -of the headers and representation of the body along the way. Then, the -proxy reads a response from the server and sends it back to the client, -also with the possibility of changing the headers and body representation. - -The example that follows implements a synchronous HTTP relay. It uses a -fixed size buffer, to avoid reading in the entire body so that the upstream -connection sees a header without unnecessary latency. This example brings -together all of the concepts discussed so far, it uses both a __serializer__ -and a __parser__ to achieve its goal: - -[http_sample_relay] +[tip + Serializing to a `std::ostream` could be implemented using an alternate + strategy: adapt the `std::ostream` interface to a __SyncWriteStream__. + This lets all the library's existing algorithms work on `std::ostream`. + We leave this as an exercise for the reader. +] [endsect] diff --git a/doc/6_1_file_body.qbk b/doc/6_1_file_body.qbk new file mode 100644 index 00000000..39f97ca4 --- /dev/null +++ b/doc/6_1_file_body.qbk @@ -0,0 +1,52 @@ +[/ + Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +] + +[section File Body Type] + +Use of the flexible __Body__ concept customization point enables authors to +preserve the self-contained nature of the __message__ object while allowing +domain specific behaviors. Common operations for HTTP servers include sending +responses which deliver file contents, and allowing for file uploads. In this +example we build the `file_body` type which supports both reading and writing +to a file on the file system. + +First we declare the type itself, along with the required members: + +[http_sample_file_body_1] + +The `size` function is a simple call to retrieve the file size: + +[http_sample_file_body_2] + +Our implementation of __BodyReader__ will contain a small buffer +from which the file contents are read. The buffer is provided to +the implementation on each call until everything has been read in. + +[http_sample_file_body_3] + +And here are the definitions for the functions we have declared: + +[http_sample_file_body_4] + +Files can be read now, and the next step is to allow writing to files +by implementing the __BodyWriter__. The style is similar to the reader, +except that buffers are incoming instead of outgoing. Here's the +declaration: + +[http_sample_file_body_5] + +Finally, here is the implementation of the writer member functions: + +[http_sample_file_body_6] + +We have created a full featured body type capable of reading and +writing files on the filesystem, integrating seamlessly with the +HTTP algorithms and message container. Source code for this body +type, and HTTP servers that use it, are available in the examples +directory. + +[endsect] diff --git a/doc/6_0_websocket.qbk b/doc/7_0_websocket.qbk similarity index 86% rename from doc/6_0_websocket.qbk rename to doc/7_0_websocket.qbk index da97aea1..85621f0c 100644 --- a/doc/6_0_websocket.qbk +++ b/doc/7_0_websocket.qbk @@ -27,12 +27,12 @@ Boost.Asio with a consistent asynchronous model using a modern C++ approach. [ws_snippet_1] ] -[include 6_1_streams.qbk] -[include 6_2_connect.qbk] -[include 6_3_client.qbk] -[include 6_4_server.qbk] -[include 6_5_messages.qbk] -[include 6_6_control.qbk] -[include 6_7_notes.qbk] +[include 7_1_streams.qbk] +[include 7_2_connect.qbk] +[include 7_3_client.qbk] +[include 7_4_server.qbk] +[include 7_5_messages.qbk] +[include 7_6_control.qbk] +[include 7_7_notes.qbk] [endsect] diff --git a/doc/6_1_streams.qbk b/doc/7_1_streams.qbk similarity index 100% rename from doc/6_1_streams.qbk rename to doc/7_1_streams.qbk diff --git a/doc/6_2_connect.qbk b/doc/7_2_connect.qbk similarity index 100% rename from doc/6_2_connect.qbk rename to doc/7_2_connect.qbk diff --git a/doc/6_3_client.qbk b/doc/7_3_client.qbk similarity index 100% rename from doc/6_3_client.qbk rename to doc/7_3_client.qbk diff --git a/doc/6_4_server.qbk b/doc/7_4_server.qbk similarity index 100% rename from doc/6_4_server.qbk rename to doc/7_4_server.qbk diff --git a/doc/6_5_messages.qbk b/doc/7_5_messages.qbk similarity index 100% rename from doc/6_5_messages.qbk rename to doc/7_5_messages.qbk diff --git a/doc/6_6_control.qbk b/doc/7_6_control.qbk similarity index 100% rename from doc/6_6_control.qbk rename to doc/7_6_control.qbk diff --git a/doc/6_7_notes.qbk b/doc/7_7_notes.qbk similarity index 100% rename from doc/6_7_notes.qbk rename to doc/7_7_notes.qbk diff --git a/doc/7_concepts.qbk b/doc/8_concepts.qbk similarity index 99% rename from doc/7_concepts.qbk rename to doc/8_concepts.qbk index b34fcba0..6f49bb42 100644 --- a/doc/7_concepts.qbk +++ b/doc/8_concepts.qbk @@ -7,8 +7,6 @@ [section:concept Concepts] - - [include concept/Body.qbk] [include concept/BodyReader.qbk] [include concept/BodyWriter.qbk] diff --git a/doc/8_0_design.qbk b/doc/9_0_design.qbk similarity index 94% rename from doc/8_0_design.qbk rename to doc/9_0_design.qbk index 7b2f1b3a..45547cd8 100644 --- a/doc/8_0_design.qbk +++ b/doc/9_0_design.qbk @@ -50,9 +50,9 @@ start. Other design goals: * Allow for customizations, if the user needs it. -[include 8_1_http_message.qbk] -[include 8_2_http_comparison.qbk] -[include 8_3_websocket_zaphoyd.qbk] -[include 8_4_faq.qbk] +[include 9_1_http_message.qbk] +[include 9_2_http_comparison.qbk] +[include 9_3_websocket_zaphoyd.qbk] +[include 9_4_faq.qbk] [endsect] diff --git a/doc/8_1_http_message.qbk b/doc/9_1_http_message.qbk similarity index 100% rename from doc/8_1_http_message.qbk rename to doc/9_1_http_message.qbk diff --git a/doc/8_2_http_comparison.qbk b/doc/9_2_http_comparison.qbk similarity index 100% rename from doc/8_2_http_comparison.qbk rename to doc/9_2_http_comparison.qbk diff --git a/doc/8_3_websocket_zaphoyd.qbk b/doc/9_3_websocket_zaphoyd.qbk similarity index 100% rename from doc/8_3_websocket_zaphoyd.qbk rename to doc/9_3_websocket_zaphoyd.qbk diff --git a/doc/8_4_faq.qbk b/doc/9_4_faq.qbk similarity index 100% rename from doc/8_4_faq.qbk rename to doc/9_4_faq.qbk diff --git a/examples/doc_core_samples.hpp b/examples/doc_core_samples.hpp new file mode 100644 index 00000000..8a1b700a --- /dev/null +++ b/examples/doc_core_samples.hpp @@ -0,0 +1,469 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the 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 + +//------------------------------------------------------------------------------ +// +// Example: Detect TLS/SSL +// +//------------------------------------------------------------------------------ + +//[core_sample_detect_tls_1 + +#include +#include + +/** Return `true` if a buffer contains a TLS/SSL client handshake. + + This function returns `true` if the beginning of the buffer + indicates that a TLS handshake is being negotiated, and that + there are at least four octets in the buffer. + + If the content of the buffer cannot possibly be a TLS handshake + request, the function returns `false`. Otherwise, if additional + octets are required, `boost::indeterminate` is returned. + + @param buffer The input buffer to inspect. This type must meet + the requirements of @b ConstBufferSequence. + + @return `boost::tribool` indicating whether the buffer contains + a TLS client handshake, does not contain a handshake, or needs + additional octets. + + @see + + http://www.ietf.org/rfc/rfc2246.txt + 7.4. Handshake protocol +*/ +template +boost::tribool +is_ssl_handshake(ConstBufferSequence const& buffers); + +//] + +using namespace beast; + +//[core_sample_detect_tls_2 + +template< + class ConstBufferSequence> +boost::tribool +is_ssl_handshake( + ConstBufferSequence const& buffers) +{ + // Make sure buffers meets the requirements + static_assert(is_const_buffer_sequence::value, + "ConstBufferSequence requirements not met"); + + // We need at least one byte to really do anything + if(boost::asio::buffer_size(buffers) < 1) + return boost::indeterminate; + + // Extract the first byte, which holds the + // "message" type for the Handshake protocol. + unsigned char v; + boost::asio::buffer_copy(boost::asio::buffer(&v, 1), buffers); + + // Check that the message type is "SSL Handshake" (rfc2246) + if(v != 0x16) + { + // This is definitely not a handshake + return false; + } + + // At least four bytes are needed for the handshake + // so make sure that we get them before returning `true` + if(boost::asio::buffer_size(buffers) < 4) + return boost::indeterminate; + + // This can only be a TLS/SSL handshake + return true; +} + +//] + +//[core_sample_detect_tls_3 + +/** Detect a TLS/SSL handshake on a stream. + + This function reads from a stream to determine if a TLS/SSL + handshake is being received. The function call will block + until one of the following conditions is true: + + @li The disposition of the handshake is determined + + @li An error occurs + + Octets read from the stream will be stored in the passed dynamic + buffer, which may be used to perform the TLS handshake if the + detector returns true, or otherwise consumed by the caller based + on the expected protocol. + + @param stream The stream to read from. This type must meet the + requirements of @b SyncReadStream. + + @param buffer The dynamic buffer to use. This type must meet the + requirements of @b DynamicBuffer. + + @param ec Set to the error if any occurred. + + @return `boost::tribool` indicating whether the buffer contains + a TLS client handshake, does not contain a handshake, or needs + additional octets. If an error occurs, the return value is + undefined. +*/ +template< + class SyncReadStream, + class DynamicBuffer> +boost::tribool +detect_ssl( + SyncReadStream& stream, + DynamicBuffer& buffer, + error_code& ec) +{ + // Make sure arguments meet the requirements + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + + // Loop until an error occurs or we get a definitive answer + for(;;) + { + // There could already be data in the buffer + // so we do this first, before reading from the stream. + auto const result = is_ssl_handshake(buffer.data()); + + // If we got an answer, return it + if(! boost::indeterminate(result)) + return result; + + // The algorithm should never need more than 4 bytes + BOOST_ASSERT(buffer.size() < 4); + + // We need more bytes, but no more than four total. + buffer.commit(stream.read_some(buffer.prepare(4 - buffer.size()), ec)); + + // Check for an error + if(ec) + break; + } + + // error + return false; +} + +//] + +//[core_sample_detect_tls_4 + +/** Detect a TLS/SSL handshake asynchronously on a stream. + + This function is used to asynchronously determine if a TLS/SSL + handshake is being received. + The function call always returns immediately. The asynchronous + operation will continue until one of the following conditions + is true: + + @li The disposition of the handshake is determined + + @li An error occurs + + This operation is implemented in terms of zero or more calls to + the next layer's `async_read_some` function, and is known as a + composed operation. The program must ensure that the + stream performs no other operations until this operation completes. + + Octets read from the stream will be stored in the passed dynamic + buffer, which may be used to perform the TLS handshake if the + detector returns true, or otherwise consumed by the caller based + on the expected protocol. + + @param stream The stream to read from. This type must meet the + requirements of @b AsyncReadStream. + + @param buffer The dynamic buffer to use. This type must meet the + requirements of @b DynamicBuffer. + + @param handler The handler to be called when the request + completes. Copies will be made of the handler as required. + The equivalent function signature of the handler must be: + @code + void handler( + error_code const& error, // Set to the error, if any + boost::tribool result // The result of the detector + ); + @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using `boost::asio::io_service::post`. +*/ +template< + class AsyncReadStream, + class DynamicBuffer, + class CompletionToken> +async_return_type< /*< The [link beast.ref.async_return_type `async_return_type`] customizes the return value based on the completion token >*/ + CompletionToken, + void(error_code, boost::tribool)> /*< This is the signature for the completion handler >*/ +async_detect_ssl( + AsyncReadStream& stream, + DynamicBuffer& buffer, + CompletionToken&& token); + +//] + +//[core_sample_detect_tls_5 + +// This is the composed operation. +template< + class AsyncReadStream, + class DynamicBuffer, + class Handler> +class detect_ssl_op; + +// Here is the implementation of the asynchronous initation function +template< + class AsyncReadStream, + class DynamicBuffer, + class CompletionToken> +async_return_type< + CompletionToken, + void(error_code, boost::tribool)> +async_detect_ssl( + AsyncReadStream& stream, + DynamicBuffer& buffer, + CompletionToken&& token) +{ + // Make sure arguments meet the requirements + static_assert(is_async_read_stream::value, + "SyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + + // This helper manages some of the handler's lifetime and + // uses the result and handler specializations associated with + // the completion token to help customize the return value. + // + beast::async_completion< + CompletionToken, void(beast::error_code, boost::tribool)> init{token}; + + // Create the composed operation and launch it. This is a constructor + // call followed by invocation of operator(). We use handler_type + // to convert the completion token into the correct handler type, + // allowing user defined specializations of the async result template + // to take effect. + // + detect_ssl_op>{ + stream, buffer, init.completion_handler}( + beast::error_code{}, 0); + + // This hook lets the caller see a return value when appropriate. + // For example this might return std::future if + // CompletionToken is boost::asio::use_future. + // + // If a coroutine is used for the token, the return value from + // this function will be the `boost::tribool` representing the result. + // + return init.result.get(); +} + +//] + +//[core_sample_detect_tls_6 + +// Read from a stream to invoke is_tls_handshake asynchronously +// +template< + class AsyncReadStream, + class DynamicBuffer, + class Handler> +class detect_ssl_op +{ + // This composed operation has trivial state, + // so it is just kept inside the class and can + // be cheaply copied as needed by the implementation. + + // Indicates what step in the operation's state + // machine to perform next, starting from zero. + int step_ = 0; + + AsyncReadStream& stream_; + DynamicBuffer& buffer_; + Handler handler_; + boost::tribool result_ = false; + +public: + // Boost.Asio requires that handlers are CopyConstructible. + // The state for this operation is cheap to copy. + detect_ssl_op(detect_ssl_op const&) = default; + + // The constructor just keeps references the callers varaibles. + // + template + detect_ssl_op(AsyncReadStream& stream, + DynamicBuffer& buffer, DeducedHandler&& handler) + : stream_(stream) + , buffer_(buffer) + , handler_(std::forward(handler)) + { + } + + // Determines if the next asynchronous operation represents a + // continuation of the asynchronous flow of control associated + // with the final handler. If we are past step two, it means + // we have performed an asynchronous operation therefore any + // subsequent operation would represent a continuation. + // Otherwise, we propagate the handler's associated value of + // is_continuation. Getting this right means the implementation + // may schedule the invokation of the invoked functions more + // efficiently. + // + friend bool asio_handler_is_continuation(detect_ssl_op* op) + { + // This next call is structured to permit argument + // dependent lookup to take effect. + using boost::asio::asio_handler_is_continuation; + + // Always use std::addressof to pass the pointer to the handler, + // otherwise an unwanted overload of operator& may be called instead. + return op->step_ > 2 || + asio_handler_is_continuation(std::addressof(op->handler_)); + } + + // Handler hook forwarding. These free functions invoke the hooks + // associated with the final completion handler. In effect, they + // make the Asio implementation treat our composed operation the + // same way it would treat the final completion handler for the + // purpose of memory allocation and invocation. + // + // Our implementation just passes through the call to the hook + // associated with the final handler. + + friend void* asio_handler_allocate(std::size_t size, detect_ssl_op* op) + { + using boost::asio::asio_handler_allocate; + return asio_handler_allocate(size, std::addressof(op->handler_)); + } + + friend void asio_handler_deallocate(void* p, std::size_t size, detect_ssl_op* op) + { + using boost::asio::asio_handler_deallocate; + return asio_handler_deallocate(p, size, std::addressof(op->handler_)); + } + + template + friend void asio_handler_invoke(Function&& f, detect_ssl_op* op) + { + using boost::asio::asio_handler_invoke; + return asio_handler_invoke(f, std::addressof(op->handler_)); + } + + // Our main entry point. This will get called as our + // intermediate operations complete. Definition below. + // + void operator()(beast::error_code ec, std::size_t bytes_transferred); +}; + +//] + +//[core_sample_detect_tls_7 + +// detect_ssl_op is callable with the signature +// void(error_code, bytes_transferred), +// allowing `*this` to be used as a ReadHandler +// +template< + class AsyncStream, + class DynamicBuffer, + class Handler> +void +detect_ssl_op:: +operator()(beast::error_code ec, std::size_t bytes_transferred) +{ + // Execute the state machine + switch(step_) + { + // Initial state + case 0: + // See if we can detect the handshake + result_ = is_ssl_handshake(buffer_.data()); + + // If there's a result, call the handler + if(! boost::indeterminate(result_)) + { + // We need to invoke the handler, but the guarantee + // is that the handler will not be called before the + // call to async_detect_ssl returns, so we must post + // the operation to the io_service. The helper function + // `bind_handler` lets us bind arguments in a safe way + // that preserves the type customization hooks of the + // original handler. + step_ = 1; + return stream_.get_io_service().post( + bind_handler(std::move(*this), ec, 0)); + } + + // The algorithm should never need more than 4 bytes + BOOST_ASSERT(buffer_.size() < 4); + + step_ = 2; + + do_read: + // We need more bytes, but no more than four total. + return stream_.async_read_some(buffer_.prepare(4 - buffer_.size()), std::move(*this)); + + case 1: + // Call the handler + break; + + case 2: + // Set this so that asio_handler_is_continuation knows that + // the next asynchronous operation represents a continuation + // of the initial asynchronous operation. + step_ = 3; + BOOST_FALLTHROUGH; + + case 3: + if(ec) + { + // Deliver the error to the handler + result_ = false; + + // We don't need bind_handler here because we were invoked + // as a result of an intermediate asynchronous operation. + break; + } + + // Commit the bytes that we read + buffer_.commit(bytes_transferred); + + // See if we can detect the handshake + result_ = is_ssl_handshake(buffer_.data()); + + // If it is detected, call the handler + if(! boost::indeterminate(result_)) + { + // We don't need bind_handler here because we were invoked + // as a result of an intermediate asynchronous operation. + break; + } + + // Read some more + goto do_read; + } + + // Invoke the final handler. + handler_(ec, result_); +} + +//] + +//------------------------------------------------------------------------------ diff --git a/examples/doc_http_samples.hpp b/examples/doc_http_samples.hpp index 9cfacffd..41e2184c 100644 --- a/examples/doc_http_samples.hpp +++ b/examples/doc_http_samples.hpp @@ -57,7 +57,7 @@ send_expect_100_continue( "DynamicBuffer requirements not met"); // Insert or replace the Expect field - req.replace("Expect", "100-continue"); + req.replace(field::expect, "100-continue"); // Create the serializer auto sr = make_serializer(req); @@ -126,7 +126,7 @@ receive_expect_100_continue( return; // Check for the Expect field value - if(parser.get()["Expect"] == "100-continue") + if(parser.get()[field::expect] == "100-continue") { // send 100 response response res; @@ -809,8 +809,8 @@ do_form_request( case verb::post: { // If this is not a form upload then use a string_body - if( req0.get()["Content-Type"] != "application/x-www-form-urlencoded" && - req0.get()["Content-Type"] != "multipart/form-data") + if( req0.get()[field::content_type] != "application/x-www-form-urlencoded" && + req0.get()[field::content_type] != "multipart/form-data") goto do_string_body; // Commit to string_body as the body type. diff --git a/examples/file_body.hpp b/examples/file_body.hpp index a0d9505c..d8d5de8d 100644 --- a/examples/file_body.hpp +++ b/examples/file_body.hpp @@ -10,94 +10,388 @@ #include #include -#include -#include #include +#include #include #include #include #include +#include namespace beast { namespace http { +//[http_sample_file_body_1 + struct file_body { - using value_type = std::string; + /** The type of the @ref message::body member. - /// Returns the content length of the body in a message. + Messages declared using `file_body` will have this + type for the body member. We use a path indicating + the location on the file system for which the data + will be read or written. + */ + using value_type = boost::filesystem::path; + + /** Returns the content length of the body in a message. + + This optional static function returns the size of the + body in bytes. It is called from @ref message::size to + return the payload size, and from @ref message::prepare + to automatically set the Content-Length field. If this + function is omitted from a body type, calls to + @ref message::prepare will set the chunked transfer + encoding. + + @param m The message containing a file body to check. + + @return The size of the file in bytes. + */ template static std::uint64_t - size( - message const& m) + size(message const& m); + + /** Algorithm for retrieving buffers when serializing. + + Objects of this type are created during serialization + to extract the buffers representing the body. + */ + class reader; + + /** Algorithm for storing buffers when parsing. + + Objects of this type are created during parsing + to store incoming buffers representing the body. + */ + class writer; +}; + +//] + +//[http_sample_file_body_2 + +template +std::uint64_t +file_body:: +size(message const& m) +{ + return boost::filesystem::file_size(m.body); +} + +//] + +//[http_sample_file_body_3 + +class file_body::reader +{ + value_type const& path_; // Path of the file + FILE* file_ = nullptr; // File handle + std::uint64_t remain_ = 0; // The number of unread bytes + char buf_[4096]; // Small buffer for reading + +public: + // This nested type informs the serializer that it should + // wait until after sending the header to initialize the + // reader. We set this to true, otherwise opening the file + // during `init` could introduce latency which delays + // the remote endpoint from receiving the header quickly. + // + using is_deferred = std::true_type; + + // The type of buffer sequence returned by `get`. + // + using const_buffers_type = + boost::asio::const_buffers_1; + + // Constructor. + // + // This is called after the header is serialized, because + // we declared `is_deferred` to be `std::true_type`. + // `m` holds the message we are sending, which will + // always have the `file_body` as the body type. + // + template + reader(message const& m); + + // Destructor + ~reader(); + + // This function is called once before serialization + // of the body is started. + // + void + init(error_code& ec); + + // This function is called zero or more times to + // retrieve buffers. A return value of `boost::none` + // means there are no more buffers. Otherwise, + // the contained pair will have the next buffer + // to serialize, and a `bool` indicating whether + // or not there may be additional buffers. + boost::optional> + get(error_code& ec); + + // This function is called when reading is complete. + // It is an opportunity to perform any final actions + // which might fail, in order to return an error code. + // Operations that might fail should not be attemped in + // destructors, since an exception thrown from there + // would terminate the program. + void + finish(error_code& ec); +}; + +//] + +//[http_sample_file_body_4 + +// Here we just stash a reference to the path for later. +// Rather than dealing with messy constructor exceptions, +// we save the things that might fail for the call to `init`. +// +template +file_body::reader:: +reader(message const& m) + : path_(m.body) +{ +} + +// This gets called right after construction, and provides +// the opportunity to return an error code. The error code +// will be propagated to the serializer and eventually +// returned to the caller. +// +inline +void +file_body::reader:: +init(error_code& ec) +{ + // Attempt to open the file for reading + file_ = fopen(path_.string().c_str(), "rb"); + + if(! file_) { - return boost::filesystem::file_size(m.body.c_str()); + // Convert the old-school `errno` into + // an error code using the system category. + ec = error_code{errno, system_category()}; + return; } - class reader + // The file was opened successfully, get the size + // of the file to know how much we need to read. + remain_ = boost::filesystem::file_size(path_); +} + +// This function is called repeatedly by the serializer to +// retrieve the buffers representing the body. Our strategy +// is to read into our buffer and return it until we have +// read through the whole file. +// +inline +auto +file_body::reader:: +get(error_code& ec) -> + boost::optional> +{ + // Calculate the smaller of our buffer size, + // or the amount of unread data in the file. + auto const amount = std::min(remain_, sizeof(buf_)); + + // Check for an empty file + if(amount == 0) + return boost::none; + + // Now read the next buffer + auto const nread = fread(buf_, 1, amount, file_); + + // Handle any errors + if(ferror(file_)) { - std::uint64_t size_ = 0; - std::uint64_t offset_ = 0; - std::string const& path_; - FILE* file_ = nullptr; - char buf_[4096]; + // Convert old-school `errno` to error_code + ec = error_code(errno, system_category()); + return boost::none; + } - public: - using is_deferred = std::true_type; + // Make sure there is forward progress + BOOST_ASSERT(nread != 0); + BOOST_ASSERT(nread <= remain_); - using const_buffers_type = - boost::asio::const_buffers_1; + // Update the amount remaining based on what we got + remain_ -= nread; - reader(reader&&) = default; - reader(reader const&) = delete; - reader& operator=(reader const&) = delete; + // Return the buffer to the caller. + // + // The second element of the pair indicates whether or + // not there is more data. As long as there is some + // unread bytes, there will be more data. Otherwise, + // we set this bool to `false` so we will not be called + // again. + // + return {{ + const_buffers_type{buf_, nread}, // buffer to return. + remain_ > 0 // `true` if there are more buffers. + }}; +} - template - reader(message const& m) - : path_(m.body) - { - } +// Called after reading is done when there's no error. +inline +void +file_body::reader:: +finish(error_code& ec) +{ +} - ~reader() - { - if(file_) - fclose(file_); - } +// The destructor is always invoked if construction succeeds. +// +inline +file_body::reader:: +~reader() +{ + // Just close the file if its open + if(file_) + fclose(file_); - void - init(error_code& ec) - { - file_ = fopen(path_.c_str(), "rb"); - if(! file_) - ec = error_code{errno, system_category()}; - else - size_ = boost::filesystem::file_size(path_); - } + // In theory fclose() can fail but how would we handle it? +} - boost::optional> - get(error_code& ec) - { - auto const amount = std::min(size_ - offset_, sizeof(buf_)); - auto const nread = fread(buf_, 1, amount, file_); - if(ferror(file_)) - { - ec = error_code(errno, system_category()); - return boost::none; - } - BOOST_ASSERT(nread != 0); - offset_ += nread; - return {{const_buffers_type{buf_, nread}, offset_ < size_}}; - } +//] - void - finish(error_code&) - { - } - }; +//[http_sample_file_body_5 + +class file_body::writer +{ + value_type const& path_; // A path to the file + FILE* file_ = nullptr; // The file handle + +public: + // Constructor. + // + // This is called after the header is parsed and + // indicates that a non-zero sized body may be present. + // `m` holds the message we are receiving, which will + // always have the `file_body` as the body type. + // + template + explicit + writer(message& m); + + // This function is called once before parsing + // of the body is started. + // + void + init(boost::optional const& content_length, error_code& ec); + + // This function is called one or more times to store + // buffer sequences corresponding to the incoming body. + // + template + void + put(ConstBufferSequence const& buffers, error_code& ec); + + // This function is called when writing is complete. + // It is an opportunity to perform any final actions + // which might fail, in order to return an error code. + // Operations that might fail should not be attemped in + // destructors, since an exception thrown from there + // would terminate the program. + // + void + finish(error_code& ec); + + // Destructor. + // + // Avoid calling anything that might fail here. + // + ~writer(); }; +//] + +//[http_sample_file_body_6 + +// Just stash a reference to the path so we can open the file later. +template +file_body::writer:: +writer(message& m) + : path_(m.body) +{ +} + +// This gets called once when we know there's a body. +// If the content_length is set, it lets us know the exact size +// of the body. An implementation could use this to optimize its +// storage strategy. For example by attempting to reserve space +// ahead of time. +// +inline +void +file_body::writer:: +init(boost::optional const& content_length, error_code& ec) +{ + // Attempt to open the file for writing + file_ = fopen(path_.string().c_str(), "wb"); + + if(! file_) + { + // Convert the old-school `errno` into + // an error code using the system category. + ec = error_code{errno, system_category()}; + return; + } +} + +// This will get called one or more times with body buffers +// +template +void +file_body::writer:: +put(ConstBufferSequence const& buffers, error_code& ec) +{ + // Loop over all the buffers in the sequence, + // and write each one to the file. + for(auto const& buffer : buffers) + { + // Write this buffer to the file + fwrite( + boost::asio::buffer_cast(buffer), 1, + boost::asio::buffer_size(buffer), + file_); + + // Handle any errors + if(ferror(file_)) + { + // Convert old-school `errno` to error_code + ec = error_code(errno, system_category()); + return; + } + } +} + +// Called after writing is done when there's no error. +inline +void +file_body::writer:: +finish(error_code& ec) +{ +} + +// The destructor is always invoked if construction succeeds +// +inline +file_body::writer:: +~writer() +{ + // Just close the file if its open + if(file_) + fclose(file_); + + // In theory fclose() can fail but how would we handle it? +} + +//] + } // http } // beast diff --git a/examples/http_async_server.hpp b/examples/http_async_server.hpp index ce884f09..69640906 100644 --- a/examples/http_async_server.hpp +++ b/examples/http_async_server.hpp @@ -207,7 +207,8 @@ private: void fail(error_code ec, std::string what) { - if(ec != boost::asio::error::operation_aborted) + if(ec != boost::asio::error::operation_aborted && + ec != error::end_of_stream) server_.log("#", id_, " ", what, ": ", ec.message(), "\n"); } diff --git a/examples/http_sync_server.hpp b/examples/http_sync_server.hpp index ed82d9d1..6f017577 100644 --- a/examples/http_sync_server.hpp +++ b/examples/http_sync_server.hpp @@ -105,7 +105,7 @@ private: fail(int id, error_code const& ec) { if(ec != boost::asio::error::operation_aborted && - ec != boost::asio::error::eof) + ec != error::end_of_stream) log("#", id, " ", ec.message(), "\n"); } diff --git a/include/beast/http/fields.hpp b/include/beast/http/fields.hpp index 2b9cabd2..1d3343ea 100644 --- a/include/beast/http/fields.hpp +++ b/include/beast/http/fields.hpp @@ -225,6 +225,16 @@ public: iterator find(string_view name) const; + /** Returns an iterator to the case-insensitive matching field. + + If more than one field with the specified name exists, the + first field defined by insertion order is returned. + + @param name The field to find. + */ + iterator + find(field name) const; + /** Returns the value for a case-insensitive matching header, or `""`. If more than one field with the specified name exists, the @@ -233,6 +243,16 @@ public: string_view const operator[](string_view name) const; + /** Returns the value for a field, or `""` if it does not exist. + + If more than one field with the specified name exists, the + first field defined by insertion order is returned. + + @param name The field to retrieve. + */ + string_view const + operator[](field name) const; + /// Clear the contents of the basic_fields. void clear() noexcept; @@ -319,6 +339,18 @@ public: void replace(string_view name, string_view value); + /** Replace a field value. + + First removes any values with matching field names, then + inserts the new field value. + + @param name The field to replace. + + @param value A string holding the value of the field. + */ + void + replace(field name, string_view value); + /** Replace a field value. First removes any values with matching field names, then diff --git a/include/beast/http/impl/fields.ipp b/include/beast/http/impl/fields.ipp index a2fffa24..7daa88ed 100644 --- a/include/beast/http/impl/fields.ipp +++ b/include/beast/http/impl/fields.ipp @@ -467,6 +467,19 @@ find(string_view name) const -> return list_.iterator_to(*it); } +template +auto +basic_fields:: +find(field name) const -> + iterator +{ + auto const it = set_.find( + to_string(name), less{}); + if(it == set_.end()) + return list_.end(); + return list_.iterator_to(*it); +} + template string_view const basic_fields:: @@ -478,6 +491,17 @@ operator[](string_view name) const return it->value(); } +template +string_view const +basic_fields:: +operator[](field name) const +{ + auto const it = find(name); + if(it == end()) + return {}; + return it->value(); +} + template void basic_fields:: @@ -547,6 +571,16 @@ replace(string_view name, string_view value) insert(name, value); } +template +void +basic_fields:: +replace(field name, string_view value) +{ + value = detail::trim(value); + erase(name); + insert(name, value); +} + //------------------------------------------------------------------------------ // Fields diff --git a/test/Jamfile b/test/Jamfile index 9136c85b..50ecfd9a 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -25,6 +25,7 @@ unit-test core-tests : core/buffers_adapter.cpp core/clamp.cpp core/consuming_buffers.cpp + core/doc_core_samples.cpp core/doc_snippets.cpp core/error.cpp core/flat_buffer.cpp diff --git a/test/core/CMakeLists.txt b/test/core/CMakeLists.txt index 76bc6c22..95911dce 100644 --- a/test/core/CMakeLists.txt +++ b/test/core/CMakeLists.txt @@ -1,11 +1,13 @@ # Part of Beast +GroupSources(examples examples) GroupSources(extras/beast extras) GroupSources(include/beast beast) GroupSources(test/core "/") add_executable (core-tests ${BEAST_INCLUDES} + ${EXAMPLES_INCLUDES} ${EXTRAS_INCLUDES} ../../extras/beast/unit_test/main.cpp async_result.cpp @@ -18,6 +20,7 @@ add_executable (core-tests buffers_adapter.cpp clamp.cpp consuming_buffers.cpp + doc_core_samples.cpp doc_snippets.cpp error.cpp flat_buffer.cpp diff --git a/test/core/doc_core_samples.cpp b/test/core/doc_core_samples.cpp new file mode 100644 index 00000000..e669e68a --- /dev/null +++ b/test/core/doc_core_samples.cpp @@ -0,0 +1,85 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include + +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +class doc_core_samples_test + : public beast::unit_test::suite + , public beast::test::enable_yield_to +{ +public: + void + testDetect() + { + char buf[4]; + buf[0] = 0x16; + buf[1] = 0; + buf[2] = 0; + buf[3] = 0; + BEAST_EXPECT(boost::indeterminate(is_ssl_handshake( + boost::asio::buffer(buf, 0)))); + BEAST_EXPECT(boost::indeterminate(is_ssl_handshake( + boost::asio::buffer(buf, 1)))); + BEAST_EXPECT(boost::indeterminate(is_ssl_handshake( + boost::asio::buffer(buf, 2)))); + BEAST_EXPECT(boost::indeterminate(is_ssl_handshake( + boost::asio::buffer(buf, 3)))); + BEAST_EXPECT(is_ssl_handshake( + boost::asio::buffer(buf, 4))); + buf[0] = 0; + BEAST_EXPECT(! is_ssl_handshake( + boost::asio::buffer(buf, 1))); + } + + void + testRead() + { + { + test::pipe p{ios_}; + ostream(p.server.buffer) << + "\x16***"; + error_code ec; + flat_buffer b; + auto const result = detect_ssl(p.server, b, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(result); + } + yield_to( + [&](yield_context yield) + { + test::pipe p{ios_}; + ostream(p.server.buffer) << + "\x16***"; + error_code ec; + flat_buffer b; + auto const result = async_detect_ssl(p.server, b, yield[ec]); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(result); + }); + } + + void + run() + { + testDetect(); + testRead(); + } +}; + +BEAST_DEFINE_TESTSUITE(doc_core_samples,core,beast); + +} // http +} // beast diff --git a/test/http/doc_http_samples.cpp b/test/http/doc_http_samples.cpp index 72c45462..5153f57b 100644 --- a/test/http/doc_http_samples.cpp +++ b/test/http/doc_http_samples.cpp @@ -6,6 +6,7 @@ // #include +#include #include #include @@ -257,6 +258,56 @@ public: //-------------------------------------------------------------------------- + void + doFileBody() + { + test::pipe c{ios_}; + + boost::filesystem::path const path = "temp.txt"; + std::string const body = "Hello, world!\n"; + { + request req; + req.version = 11; + req.method(verb::put); + req.target("/"); + req.body = body; + req.prepare(); + write(c.client, req); + } + { + flat_buffer b; + request_parser p0; + read_header(c.server, b, p0); + BEAST_EXPECTS(p0.get().method() == verb::put, + p0.get().method_string()); + { + request_parser p{std::move(p0)}; + p.get().body = path; + read(c.server, b, p); + } + } + { + response res; + res.version = 11; + res.result(status::ok); + res.insert(field::server, "test"); + res.body = path; + res.prepare(); + write(c.server, res); + } + { + flat_buffer b; + response res; + read(c.client, b, res); + BEAST_EXPECTS(res.body == body, body); + } + error_code ec; + boost::filesystem::remove(path, ec); + BEAST_EXPECTS(! ec, ec.message()); + } + + //-------------------------------------------------------------------------- + void run() { @@ -268,6 +319,7 @@ public: doCustomParser(); doHEAD(); doDeferredBody(); + doFileBody(); } }; diff --git a/test/websocket/doc_snippets.cpp b/test/websocket/doc_snippets.cpp index 875bb3ed..d1ee623d 100644 --- a/test/websocket/doc_snippets.cpp +++ b/test/websocket/doc_snippets.cpp @@ -216,7 +216,8 @@ boost::asio::ip::tcp::socket sock{ios}; } // fxx() -#if 0 +// workaround for https://github.com/chriskohlhoff/asio/issues/112 +#ifdef _MSC_VER //[ws_snippet_21 void echo(stream& ws, multi_buffer& buffer, boost::asio::yield_context yield)