mirror of
https://github.com/boostorg/beast.git
synced 2025-08-03 14:54:32 +02:00
Documentation work
This commit is contained in:
@@ -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
|
||||
|
@@ -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]
|
||||
|
@@ -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.
|
||||
]
|
||||
|
||||
|
@@ -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]
|
||||
|
@@ -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__.
|
||||
|
||||
|
17
doc/4_0_network.qbk
Normal file
17
doc/4_0_network.qbk
Normal file
@@ -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]
|
64
doc/4_1_detect_tls.qbk
Normal file
64
doc/4_1_detect_tls.qbk
Normal file
@@ -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]
|
@@ -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
|
@@ -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]
|
@@ -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]
|
||||
|
52
doc/6_1_file_body.qbk
Normal file
52
doc/6_1_file_body.qbk
Normal file
@@ -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]
|
@@ -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]
|
@@ -7,8 +7,6 @@
|
||||
|
||||
[section:concept Concepts]
|
||||
|
||||
|
||||
|
||||
[include concept/Body.qbk]
|
||||
[include concept/BodyReader.qbk]
|
||||
[include concept/BodyWriter.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]
|
469
examples/doc_core_samples.hpp
Normal file
469
examples/doc_core_samples.hpp
Normal file
@@ -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 <boost/assert.hpp>
|
||||
#include <boost/config.hpp>
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Example: Detect TLS/SSL
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//[core_sample_detect_tls_1
|
||||
|
||||
#include <beast.hpp>
|
||||
#include <boost/logic/tribool.hpp>
|
||||
|
||||
/** Return `true` if a buffer contains a TLS/SSL client handshake.
|
||||
|
||||
This function returns `true` if the beginning of the buffer
|
||||
indicates that a TLS handshake is being negotiated, and that
|
||||
there are at least four octets in the buffer.
|
||||
|
||||
If the content of the buffer cannot possibly be a TLS handshake
|
||||
request, the function returns `false`. Otherwise, if additional
|
||||
octets are required, `boost::indeterminate` is returned.
|
||||
|
||||
@param buffer The input buffer to inspect. This type must meet
|
||||
the requirements of @b ConstBufferSequence.
|
||||
|
||||
@return `boost::tribool` indicating whether the buffer contains
|
||||
a TLS client handshake, does not contain a handshake, or needs
|
||||
additional octets.
|
||||
|
||||
@see
|
||||
|
||||
http://www.ietf.org/rfc/rfc2246.txt
|
||||
7.4. Handshake protocol
|
||||
*/
|
||||
template<class ConstBufferSequence>
|
||||
boost::tribool
|
||||
is_ssl_handshake(ConstBufferSequence const& buffers);
|
||||
|
||||
//]
|
||||
|
||||
using namespace beast;
|
||||
|
||||
//[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<ConstBufferSequence>::value,
|
||||
"ConstBufferSequence requirements not met");
|
||||
|
||||
// We need at least one byte to really do anything
|
||||
if(boost::asio::buffer_size(buffers) < 1)
|
||||
return boost::indeterminate;
|
||||
|
||||
// Extract the first byte, which holds the
|
||||
// "message" type for the Handshake protocol.
|
||||
unsigned char v;
|
||||
boost::asio::buffer_copy(boost::asio::buffer(&v, 1), buffers);
|
||||
|
||||
// Check that the message type is "SSL Handshake" (rfc2246)
|
||||
if(v != 0x16)
|
||||
{
|
||||
// This is definitely not a handshake
|
||||
return false;
|
||||
}
|
||||
|
||||
// At least four bytes are needed for the handshake
|
||||
// so make sure that we get them before returning `true`
|
||||
if(boost::asio::buffer_size(buffers) < 4)
|
||||
return boost::indeterminate;
|
||||
|
||||
// This can only be a TLS/SSL handshake
|
||||
return true;
|
||||
}
|
||||
|
||||
//]
|
||||
|
||||
//[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<SyncReadStream>::value,
|
||||
"SyncReadStream requirements not met");
|
||||
static_assert(is_dynamic_buffer<DynamicBuffer>::value,
|
||||
"DynamicBuffer requirements not met");
|
||||
|
||||
// Loop until an error occurs or we get a definitive answer
|
||||
for(;;)
|
||||
{
|
||||
// There could already be data in the buffer
|
||||
// so we do this first, before reading from the stream.
|
||||
auto const result = is_ssl_handshake(buffer.data());
|
||||
|
||||
// If we got an answer, return it
|
||||
if(! boost::indeterminate(result))
|
||||
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
|
||||
<em>composed operation</em>. The program must ensure that the
|
||||
stream performs no other operations until this operation completes.
|
||||
|
||||
Octets read from the stream will be stored in the passed dynamic
|
||||
buffer, which may be used to perform the TLS handshake if the
|
||||
detector returns true, or otherwise consumed by the caller based
|
||||
on the expected protocol.
|
||||
|
||||
@param stream The stream to read from. This type must meet the
|
||||
requirements of @b AsyncReadStream.
|
||||
|
||||
@param buffer The dynamic buffer to use. This type must meet the
|
||||
requirements of @b DynamicBuffer.
|
||||
|
||||
@param handler The handler to be called when the request
|
||||
completes. Copies will be made of the handler as required.
|
||||
The equivalent function signature of the handler must be:
|
||||
@code
|
||||
void handler(
|
||||
error_code const& error, // Set to the error, if any
|
||||
boost::tribool result // The result of the detector
|
||||
);
|
||||
@endcode
|
||||
Regardless of whether the asynchronous operation completes
|
||||
immediately or not, the handler will not be invoked from within
|
||||
this function. Invocation of the handler will be performed in a
|
||||
manner equivalent to using `boost::asio::io_service::post`.
|
||||
*/
|
||||
template<
|
||||
class AsyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class CompletionToken>
|
||||
async_return_type< /*< The [link beast.ref.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<AsyncReadStream>::value,
|
||||
"SyncReadStream requirements not met");
|
||||
static_assert(is_dynamic_buffer<DynamicBuffer>::value,
|
||||
"DynamicBuffer requirements not met");
|
||||
|
||||
// This helper manages some of the handler's lifetime and
|
||||
// uses the result and handler specializations associated with
|
||||
// the completion token to help customize the return value.
|
||||
//
|
||||
beast::async_completion<
|
||||
CompletionToken, void(beast::error_code, boost::tribool)> init{token};
|
||||
|
||||
// Create the composed operation and launch it. This is a constructor
|
||||
// call followed by invocation of operator(). We use handler_type
|
||||
// to convert the completion token into the correct handler type,
|
||||
// allowing user defined specializations of the async result template
|
||||
// to take effect.
|
||||
//
|
||||
detect_ssl_op<AsyncReadStream, DynamicBuffer, handler_type<
|
||||
CompletionToken, void(error_code, boost::tribool)>>{
|
||||
stream, buffer, init.completion_handler}(
|
||||
beast::error_code{}, 0);
|
||||
|
||||
// This hook lets the caller see a return value when appropriate.
|
||||
// For example this might return std::future<error_code, boost::tribool> if
|
||||
// CompletionToken is boost::asio::use_future.
|
||||
//
|
||||
// If a coroutine is used for the token, the return value from
|
||||
// this function will be the `boost::tribool` representing the result.
|
||||
//
|
||||
return init.result.get();
|
||||
}
|
||||
|
||||
//]
|
||||
|
||||
//[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<class DeducedHandler>
|
||||
detect_ssl_op(AsyncReadStream& stream,
|
||||
DynamicBuffer& buffer, DeducedHandler&& handler)
|
||||
: stream_(stream)
|
||||
, buffer_(buffer)
|
||||
, handler_(std::forward<DeducedHandler>(handler))
|
||||
{
|
||||
}
|
||||
|
||||
// Determines if the next asynchronous operation represents a
|
||||
// continuation of the asynchronous flow of control associated
|
||||
// with the final handler. If we are past step two, it means
|
||||
// we have performed an asynchronous operation therefore any
|
||||
// subsequent operation would represent a continuation.
|
||||
// Otherwise, we propagate the handler's associated value of
|
||||
// is_continuation. Getting this right means the implementation
|
||||
// may schedule the invokation of the invoked functions more
|
||||
// efficiently.
|
||||
//
|
||||
friend bool asio_handler_is_continuation(detect_ssl_op* op)
|
||||
{
|
||||
// This next call is structured to permit argument
|
||||
// dependent lookup to take effect.
|
||||
using boost::asio::asio_handler_is_continuation;
|
||||
|
||||
// Always use std::addressof to pass the pointer to the handler,
|
||||
// otherwise an unwanted overload of operator& may be called instead.
|
||||
return op->step_ > 2 ||
|
||||
asio_handler_is_continuation(std::addressof(op->handler_));
|
||||
}
|
||||
|
||||
// Handler hook forwarding. These free functions invoke the hooks
|
||||
// associated with the final completion handler. In effect, they
|
||||
// make the Asio implementation treat our composed operation the
|
||||
// same way it would treat the final completion handler for the
|
||||
// purpose of memory allocation and invocation.
|
||||
//
|
||||
// Our implementation just passes through the call to the hook
|
||||
// associated with the final handler.
|
||||
|
||||
friend void* asio_handler_allocate(std::size_t size, detect_ssl_op* op)
|
||||
{
|
||||
using boost::asio::asio_handler_allocate;
|
||||
return asio_handler_allocate(size, std::addressof(op->handler_));
|
||||
}
|
||||
|
||||
friend void asio_handler_deallocate(void* p, std::size_t size, detect_ssl_op* op)
|
||||
{
|
||||
using boost::asio::asio_handler_deallocate;
|
||||
return asio_handler_deallocate(p, size, std::addressof(op->handler_));
|
||||
}
|
||||
|
||||
template<class Function>
|
||||
friend void asio_handler_invoke(Function&& f, detect_ssl_op* op)
|
||||
{
|
||||
using boost::asio::asio_handler_invoke;
|
||||
return asio_handler_invoke(f, std::addressof(op->handler_));
|
||||
}
|
||||
|
||||
// Our main entry point. This will get called as our
|
||||
// intermediate operations complete. Definition below.
|
||||
//
|
||||
void operator()(beast::error_code ec, std::size_t bytes_transferred);
|
||||
};
|
||||
|
||||
//]
|
||||
|
||||
//[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<AsyncStream, DynamicBuffer, Handler>::
|
||||
operator()(beast::error_code ec, std::size_t bytes_transferred)
|
||||
{
|
||||
// Execute the state machine
|
||||
switch(step_)
|
||||
{
|
||||
// Initial state
|
||||
case 0:
|
||||
// See if we can detect the handshake
|
||||
result_ = is_ssl_handshake(buffer_.data());
|
||||
|
||||
// If there's a result, call the handler
|
||||
if(! boost::indeterminate(result_))
|
||||
{
|
||||
// We need to invoke the handler, but the guarantee
|
||||
// is that the handler will not be called before the
|
||||
// call to async_detect_ssl returns, so we must post
|
||||
// the operation to the io_service. The helper function
|
||||
// `bind_handler` lets us bind arguments in a safe way
|
||||
// that preserves the type customization hooks of the
|
||||
// original handler.
|
||||
step_ = 1;
|
||||
return stream_.get_io_service().post(
|
||||
bind_handler(std::move(*this), ec, 0));
|
||||
}
|
||||
|
||||
// The algorithm should never need more than 4 bytes
|
||||
BOOST_ASSERT(buffer_.size() < 4);
|
||||
|
||||
step_ = 2;
|
||||
|
||||
do_read:
|
||||
// We need more bytes, but no more than four total.
|
||||
return stream_.async_read_some(buffer_.prepare(4 - buffer_.size()), std::move(*this));
|
||||
|
||||
case 1:
|
||||
// Call the handler
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// Set this so that asio_handler_is_continuation knows that
|
||||
// the next asynchronous operation represents a continuation
|
||||
// of the initial asynchronous operation.
|
||||
step_ = 3;
|
||||
BOOST_FALLTHROUGH;
|
||||
|
||||
case 3:
|
||||
if(ec)
|
||||
{
|
||||
// Deliver the error to the handler
|
||||
result_ = false;
|
||||
|
||||
// We don't need bind_handler here because we were invoked
|
||||
// as a result of an intermediate asynchronous operation.
|
||||
break;
|
||||
}
|
||||
|
||||
// Commit the bytes that we read
|
||||
buffer_.commit(bytes_transferred);
|
||||
|
||||
// See if we can detect the handshake
|
||||
result_ = is_ssl_handshake(buffer_.data());
|
||||
|
||||
// If it is detected, call the handler
|
||||
if(! boost::indeterminate(result_))
|
||||
{
|
||||
// We don't need bind_handler here because we were invoked
|
||||
// as a result of an intermediate asynchronous operation.
|
||||
break;
|
||||
}
|
||||
|
||||
// Read some more
|
||||
goto do_read;
|
||||
}
|
||||
|
||||
// Invoke the final handler.
|
||||
handler_(ec, result_);
|
||||
}
|
||||
|
||||
//]
|
||||
|
||||
//------------------------------------------------------------------------------
|
@@ -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<empty_body> 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.
|
||||
|
@@ -10,94 +10,388 @@
|
||||
|
||||
#include <beast/core/error.hpp>
|
||||
#include <beast/http/message.hpp>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
|
||||
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<bool isRequest, class Fields>
|
||||
static
|
||||
std::uint64_t
|
||||
size(
|
||||
message<isRequest, file_body, Fields> const& m)
|
||||
size(message<isRequest, file_body, Fields> 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<bool isRequest, class Fields>
|
||||
std::uint64_t
|
||||
file_body::
|
||||
size(message<isRequest, file_body, Fields> 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<bool isRequest, class Fields>
|
||||
reader(message<isRequest, file_body, Fields> 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<std::pair<const_buffers_type, bool>>
|
||||
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<bool isRequest, class Fields>
|
||||
file_body::reader::
|
||||
reader(message<isRequest, file_body, Fields> 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<std::pair<const_buffers_type, bool>>
|
||||
{
|
||||
// Calculate the smaller of our buffer size,
|
||||
// or the amount of unread data in the file.
|
||||
auto const amount = std::min<std::uint64_t>(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<bool isRequest, class Fields>
|
||||
reader(message<isRequest,
|
||||
file_body, Fields> 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<std::pair<const_buffers_type, bool>>
|
||||
get(error_code& ec)
|
||||
{
|
||||
auto const amount = std::min<std::uint64_t>(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<bool isRequest, class Fields>
|
||||
explicit
|
||||
writer(message<isRequest, file_body, Fields>& m);
|
||||
|
||||
// This function is called once before parsing
|
||||
// of the body is started.
|
||||
//
|
||||
void
|
||||
init(boost::optional<std::uint64_t> const& content_length, error_code& ec);
|
||||
|
||||
// This function is called one or more times to store
|
||||
// buffer sequences corresponding to the incoming body.
|
||||
//
|
||||
template<class ConstBufferSequence>
|
||||
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<bool isRequest, class Fields>
|
||||
file_body::writer::
|
||||
writer(message<isRequest, file_body, Fields>& 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<std::uint64_t> 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<class ConstBufferSequence>
|
||||
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<void const*>(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
|
||||
|
||||
|
@@ -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");
|
||||
}
|
||||
|
||||
|
@@ -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");
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -467,6 +467,19 @@ find(string_view name) const ->
|
||||
return list_.iterator_to(*it);
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
auto
|
||||
basic_fields<Allocator>::
|
||||
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<class Allocator>
|
||||
string_view const
|
||||
basic_fields<Allocator>::
|
||||
@@ -478,6 +491,17 @@ operator[](string_view name) const
|
||||
return it->value();
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
string_view const
|
||||
basic_fields<Allocator>::
|
||||
operator[](field name) const
|
||||
{
|
||||
auto const it = find(name);
|
||||
if(it == end())
|
||||
return {};
|
||||
return it->value();
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
void
|
||||
basic_fields<Allocator>::
|
||||
@@ -547,6 +571,16 @@ replace(string_view name, string_view value)
|
||||
insert(name, value);
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
void
|
||||
basic_fields<Allocator>::
|
||||
replace(field name, string_view value)
|
||||
{
|
||||
value = detail::trim(value);
|
||||
erase(name);
|
||||
insert(name, value);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Fields
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
85
test/core/doc_core_samples.cpp
Normal file
85
test/core/doc_core_samples.cpp
Normal file
@@ -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 <examples/doc_core_samples.hpp>
|
||||
|
||||
#include <beast/core/flat_buffer.hpp>
|
||||
#include <beast/core/ostream.hpp>
|
||||
#include <beast/test/pipe_stream.hpp>
|
||||
#include <beast/test/yield_to.hpp>
|
||||
#include <beast/unit_test/suite.hpp>
|
||||
|
||||
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
|
@@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
#include <examples/doc_http_samples.hpp>
|
||||
#include <examples/file_body.hpp>
|
||||
|
||||
#include <beast/core/detail/clamp.hpp>
|
||||
#include <beast/core/detail/read_size_helper.hpp>
|
||||
@@ -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<string_body> req;
|
||||
req.version = 11;
|
||||
req.method(verb::put);
|
||||
req.target("/");
|
||||
req.body = body;
|
||||
req.prepare();
|
||||
write(c.client, req);
|
||||
}
|
||||
{
|
||||
flat_buffer b;
|
||||
request_parser<empty_body> p0;
|
||||
read_header(c.server, b, p0);
|
||||
BEAST_EXPECTS(p0.get().method() == verb::put,
|
||||
p0.get().method_string());
|
||||
{
|
||||
request_parser<file_body> p{std::move(p0)};
|
||||
p.get().body = path;
|
||||
read(c.server, b, p);
|
||||
}
|
||||
}
|
||||
{
|
||||
response<file_body> 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<string_body> 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();
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -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<boost::asio::ip::tcp::socket>& ws,
|
||||
multi_buffer& buffer, boost::asio::yield_context yield)
|
||||
|
Reference in New Issue
Block a user