Documentation work

This commit is contained in:
Vinnie Falco
2017-06-08 22:03:58 -07:00
parent b49b23ee83
commit 44995bf071
46 changed files with 1264 additions and 153 deletions

View File

@@ -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

View File

@@ -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]

View File

@@ -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.
]

View File

@@ -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]

View File

@@ -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
View 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
View 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]

View File

@@ -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

View File

@@ -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]

View File

@@ -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
View 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]

View File

@@ -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]

View File

@@ -7,8 +7,6 @@
[section:concept Concepts]
[include concept/Body.qbk]
[include concept/BodyReader.qbk]
[include concept/BodyWriter.qbk]

View File

@@ -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]

View 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_);
}
//]
//------------------------------------------------------------------------------

View File

@@ -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.

View File

@@ -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

View File

@@ -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");
}

View File

@@ -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");
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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();
}
};

View File

@@ -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)