Documentation work

This commit is contained in:
Vinnie Falco
2017-06-04 17:25:55 -07:00
parent b38f8260b8
commit 4b866cea36
60 changed files with 2094 additions and 2608 deletions

View File

@ -141,6 +141,7 @@ elseif ("${VARIANT}" STREQUAL "release")
endif()
include_directories (.)
include_directories (extras)
include_directories (include)
@ -172,6 +173,10 @@ file(GLOB_RECURSE BEAST_INCLUDES
${PROJECT_SOURCE_DIR}/include/beast/*.ipp
)
file(GLOB_RECURSE EXAMPLES_INCLUDES
${PROJECT_SOURCE_DIR}/examples/*.hpp
)
file(GLOB_RECURSE EXTRAS_INCLUDES
${PROJECT_SOURCE_DIR}/extras/beast/*.hpp
${PROJECT_SOURCE_DIR}/extras/beast/*.ipp

View File

@ -47,14 +47,14 @@
[def __SyncReadStream__ [@http://www.boost.org/doc/html/boost_asio/reference/SyncReadStream.html [*SyncReadStream]]]
[def __SyncWriteStream__ [@http://www.boost.org/doc/html/boost_asio/reference/SyncWriteStream.html [*SyncWriteStream]]]
[def __AsyncStream__ [link beast.ref.streams.AsyncStream [*AsyncStream]]]
[def __Body__ [link beast.ref.Body [*Body]]]
[def __BodyReader__ [link beast.ref.BodyReader [*BodyReader]]]
[def __BodyWriter__ [link beast.ref.BodyWriter [*BodyWriter]]]
[def __DynamicBuffer__ [link beast.ref.DynamicBuffer [*DynamicBuffer]]]
[def __FieldSequence__ [link beast.ref.FieldSequence [*FieldSequence]]]
[def __Stream__ [link beast.ref.streams [*Stream]]]
[def __SyncStream__ [link beast.ref.streams.SyncStream [*SyncStream]]]
[def __AsyncStream__ [link beast.concept.streams.AsyncStream [*AsyncStream]]]
[def __Body__ [link beast.concept.Body [*Body]]]
[def __BodyReader__ [link beast.concept.BodyReader [*BodyReader]]]
[def __BodyWriter__ [link beast.concept.BodyWriter [*BodyWriter]]]
[def __DynamicBuffer__ [link beast.concept.DynamicBuffer [*DynamicBuffer]]]
[def __FieldSequence__ [link beast.concept.FieldSequence [*FieldSequence]]]
[def __Stream__ [link beast.concept.streams [*Stream]]]
[def __SyncStream__ [link beast.concept.streams.SyncStream [*SyncStream]]]
[def __basic_fields__ [link beast.ref.http__basic_fields `basic_fields`]]
[def __basic_multi_buffer__ [link beast.ref.basic_multi_buffer `basic_multi_buffer`]]
@ -69,71 +69,28 @@
[def __parser__ [link beast.ref.http__parser `parser`]]
[def __serializer__ [link beast.ref.http__serializer `serializer`]]
Beast is a cross-platform, header-only C++11 library for low-level HTTP
and WebSocket protocol programming that uses the consistent network and
asynchronous model of __Asio__.
[variablelist
[[
[link beast.overview Overview]
][
An explanation of requirements, audience, features, and credits.
]]
[[
[link beast.example Examples]
][
Examples that illustrate usage for typical scenarios.
]]
[[
[link beast.core Core Concepts]
][
Library-wide concepts for working with buffers and streams.
]]
[[
[link beast.http Using HTTP]
][
How to use the basic algorithms in your applications.
]]
[[
[link beast.websocket Using WebSocket]
][
How to use the WebSocket interfaces in your applications.
]]
[[
[link beast.design Design]
][
Rationale, comparison to other libraries, and FAQ.
]]
[[
[link beast.ref Reference]
][
Detailed class and function reference.
]]
[[
[link beast.index Index]
][
Book-style text index of Beast documentation.
]]
]
[import ../examples/http_example.cpp]
[import ../examples/websocket_example.cpp]
[import ../examples/echo_op.cpp]
[import ../examples/doc_http_samples.hpp]
[include 1_overview.qbk]
[include 2_examples.qbk]
[include 3_0_core.qbk]
[include 4_0_http.qbk]
[include 5_0_websocket.qbk]
[include 6_0_design.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]
[section:ref Reference]
[section:quickref Reference]
[xinclude quickref.xml]
[include concept/Body.qbk]
[include concept/BodyReader.qbk]
[include concept/BodyWriter.qbk]
[include concept/BufferSequence.qbk]
[include concept/DynamicBuffer.qbk]
[include concept/Field.qbk]
[include concept/FieldSequence.qbk]
[include concept/Streams.qbk]
[include reference.qbk]
[endsect]
[block'''<reference id="hidden"><title>This Page Intentionally Left Blank 1/2</title>''']
[section:ref This Page Intentionally Left Blank 2/2]
[include reference.qbk]
[endsect]
[block'''</reference>''']
[xinclude index.xml]

View File

@ -7,10 +7,19 @@
[section:overview Introduction]
Beast is a cross-platform, header-only C++11 library for low-level HTTP
and WebSocket protocol programming that uses the consistent networking
and asynchronous model of __Asio__. The design of the library achieves
these goals:
[important
Beast is a cross-platform, header-only C++11 library for low-level HTTP
and WebSocket protocol programming that uses the consistent network and
asynchronous model of __Asio__.
Beast is not an HTTP client or HTTP server, but it can be used to
build those things. The library is intended to be a foundation upon
which new libraries may be built. It is a goal that other architects
will create interoperable solutions on top of Beast. The provided
example programs shows how a client or server might be built.
]
The design of the library achieves these goals:
* [*Symmetry.] Interfaces are role-agnostic; the same interfaces can be
used to build clients, servers, or both.
@ -41,7 +50,7 @@ Beast requires:
* [*Boost.] Beast is built on Boost, especially Boost.Asio.
* [*OpenSSL.] If using TLS/Secure sockets (optional).
[note Tested compilers: msvc-14+, gcc 4.8+, clang 3.6+]
[note Supported compilers: msvc-14+, gcc 4.8+, clang 3.6+]
The library is [*header-only]. It is not necessary to add any .cpp files,
or to add commands to your build script for building Beast. To link your
@ -56,7 +65,7 @@ version that works with stand-alone Asio.
[heading Motivation]
There is a noticable shortage of high quality C++ networking libraries,
There is a noted shortage of high quality C++ networking libraries,
especially for the HTTP and WebSocket protocols. The author theorizes
that previous attempts to build such libraries focused too much on end
user needs instead of applying principles of computer science to solve
@ -93,14 +102,6 @@ of redirects, gzipped transfer encodings, caching, or proxying (to name
a few) are not directly provided, but nothing stops users from creating
these features using Beast's HTTP message types.
[important
Beast is not an HTTP client or HTTP server, but it can be used to
build both. The library is intended to be a foundation upon which
new libraries may be built. It is a goal that other architects will
build interoperable solutions on top of Beast. The provided example
code shows how a client or server may be built.
]
[heading Credits]
Boost.Asio is the inspiration behind which all of the interfaces and
@ -109,22 +110,23 @@ written to closely resemble the wording and presentation of Boost.Asio
documentation. Credit goes to Christopher Kohlhoff for the wonderful
Asio library and the ideas upon which Beast is built.
Beast would not be possible without the considerable time and patience
Beast would not be possible without the support of
[@https://www.ripple.com Ripple]
during the library's early development, or the ideas, time and patience
contributed by
David Schwartz,
Edward Hennis,
[@https://github.com/JoelKatz David Schwartz],
[@https://github.com/ximinez Edward Hennis],
[@https://github.com/howardhinnant Howard Hinnant],
Miguel Portilla,
Nikolaos Bougalis,
Scott Determan,
Scott Schurr,
and
[@https://www.ripple.com Ripple Labs]
for supporting its early development. Also thanks to
Agustín Bergé,
[@https://github.com/miguelportilla Miguel Portilla],
[@https://github.com/nbougalis Nik Bougalis],
[@https://github.com/seelabs Scott Determan],
[@https://github.com/scottschurr],
Many thanks to
[@https://github.com/K-ballo Agustín Bergé],
[@http://www.boost.org/users/people/glen_fernandes.html Glen Fernandes],
and
Peter Dimov
for helping me considerably on Slack.
[https://github.com/pdimov Peter Dimov]
for tirelessly answering questions on
[@https://cpplang.slack.com/ Cpplang-Slack].
[endsect]

View File

@ -5,86 +5,30 @@
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:example Examples]
[section:example Example Programs]
These usage examples are intended to quickly impress upon readers the
flavor of the library. They are complete programs which may be built
and run. Source code and build scripts for these programs may be found
in the examples directory.
[heading HTTP GET]
Use HTTP to request the root page from a website and print the response:
Use HTTP to make a GET request to a website and print the response:
```
#include <beast/core.hpp>
#include <beast/http.hpp>
#include <boost/asio.hpp>
#include <boost/lexical_cast.hpp>
#include <iostream>
#include <string>
[http_example_get]
int main()
{
// Normal boost::asio setup
std::string const host = "www.example.com";
boost::asio::io_service ios;
boost::asio::ip::tcp::resolver r{ios};
boost::asio::ip::tcp::socket sock{ios};
boost::asio::connect(sock,
r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"}));
// Send HTTP request using beast
beast::http::request<beast::http::string_body> req;
req.method(beast::http::verb::get);
req.target("/");
req.version = 11;
req.fields.replace("Host", host + ":" +
boost::lexical_cast<std::string>(sock.remote_endpoint().port()));
req.fields.replace("User-Agent", "Beast");
beast::http::prepare(req);
beast::http::write(sock, req);
// Receive and print HTTP response using beast
beast::flat_buffer b;
beast::http::response<beast::http::dynamic_body> res;
beast::http::read(sock, b, res);
std::cout << res << std::endl;
}
```
[heading WebSocket]
Establish a WebSocket connection, send a message and receive the reply:
```
#include <beast/core.hpp>
#include <beast/websocket.hpp>
#include <boost/asio.hpp>
#include <iostream>
#include <string>
int main()
{
// Normal boost::asio setup
std::string const host = "echo.websocket.org";
boost::asio::io_service ios;
boost::asio::ip::tcp::resolver r{ios};
boost::asio::ip::tcp::socket sock{ios};
boost::asio::connect(sock,
r.resolve(boost::asio::ip::tcp::resolver::query{host, "80"}));
[websocket_example_client_echo]
// WebSocket connect and send message using beast
beast::websocket::stream<boost::asio::ip::tcp::socket&> ws{sock};
ws.handshake(host, "/");
ws.write(boost::asio::buffer(std::string("Hello, world!")));
// Receive WebSocket message, print and close using beast
beast::multi_buffer b;
beast::websocket::opcode op;
ws.read(op, b);
ws.close(beast::websocket::close_code::normal);
std::cout << beast::buffers(b.data()) << "\n";
}
```
[heading WebSocket Echo Server]

View File

@ -5,17 +5,7 @@
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:core Core Concepts]
[block '''
<informaltable frame="all"><tgroup cols="1"><colspec colname="a"/><tbody><row><entry valign="top"><simplelist>
<member><link linkend="beast.core.asio">Working With Asio</link></member>
<member><link linkend="beast.core.streams">Stream Concepts</link></member>
<member><link linkend="beast.core.buffers">Buffer Concepts</link></member>
<member><link linkend="beast.core.async">Asynchronous Utilities</link></member>
<member><link linkend="beast.core.tutorial">Writing Composed Operations</link></member>
</simplelist></entry></row></tbody></tgroup></informaltable>
''']
[section:core Library Basics]
A goal of the library is expose implementation primitives in order that
users may build their own library-like components. These primitives include
@ -48,6 +38,6 @@ 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_tutorial.qbk]
[include 3_5_op_tutorial.qbk]
[endsect]

View File

@ -5,13 +5,21 @@
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[heading:asio Working With Asio]
[section:asio Boost.Asio Refresher]
Beast does not manage sockets, make outgoing connections, accept incoming
connections, or handle any aspect of connection management. In order to
invoke library algorithms it is necessary to first have a connected socket,
SSL stream, or other object which meets the required stream concepts. This
example is provided as a reminder of how to work with sockets:
[warning
Beast does not manage sockets, make outgoing connections,
accept incoming connections, handle timeouts, close endpoints,
do name lookups, deal with TLS certificates, perform authentication,
or otherwise handle any aspect of connection management. This is
left to the interfaces already existing on the underlying streams.
]
Library stream algorithms require an already-connected socket, SSL stream,
or other object which meets the required stream concepts and already has
communication established with an endpoint. This example is provided as a
reminder of how to work
with sockets:
```
auto host = "www.example.com";
boost::asio::ip::tcp::resolver r{ios};
@ -59,3 +67,5 @@ special meaning:
which is already connected with a remote host.
]]
]
[endsect]

View File

@ -5,9 +5,9 @@
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[heading:streams Stream Concepts]
[section:streams Using Streams]
A __Stream__ is communication channel where data expressed as octet
A __Stream__ is a communication channel where data expressed as octet
buffers is transferred sequentially. Streams are either synchronous
or asynchronous, and may allow reading, writing, or both. Note that
a particular type may model more than one concept. For example, the
@ -127,3 +127,5 @@ synchronous stream may check its argument:
boost::asio::write(stream, boost::asio::const_buffers_1(s.data(), s.size()));
}
```
[endsect]

View File

@ -5,7 +5,7 @@
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[heading:buffers Buffer Concepts]
[section:buffers Using Buffers]
__Asio__ provides the __ConstBufferSequence__ and __MutableBufferSequence__
concepts, whose models provide ranges of buffers, as well as the __streambuf__
@ -82,8 +82,8 @@ a special type of range. Algorithms and wrappers are provided which
transform these buffer sequences ranges efficiently using lazy evaluation.
No memory allocations are used in the transformations; instead, they
create lightweight iterators over the existing, unmodified memory
buffers. The lifetime of the transformed buffers is retained by the
caller; ownership is not transferred.
buffers. Control of buffers is retained by the caller; ownership is
not transferred.
[table Buffer Algorithms
[[Name][Description]]
@ -145,3 +145,5 @@ output streams.
dynamic buffer.
]]
]
[endsect]

View File

@ -5,7 +5,7 @@
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[heading:async Asynchronous Utilities]
[section:async Asynchronous I/O]
Asynchronous operations are started by calling a free function or member
function known as an ['asynchronous initiation function]. The initiation
@ -98,3 +98,5 @@ initiation functions used to launch them are available:
Extensible Asynchronous Model.
]]
]
[endsect]

66
doc/3_5_op_tutorial.qbk Normal file
View File

@ -0,0 +1,66 @@
[/
Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
Distributed under the Boost Software License, Version 1.0. (See accompanying
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:op_tutorial Writing a 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
[*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.
First we define the input parameters and results, then declare our initiation
function. For our echo operation the only inputs are the stream and the
completion token. The output is the error code which is usually included in
all completion handler signatures.
[core_sample_echo_op_1]
Now that we have a declaration, we will define the body of the function. We
want to achieve the following goals: perform static type checking on the input
parameters, set up the return value as per __N3747__, and launch the composed
operation by constructing the object and invoking it.
[core_sample_echo_op_2]
The initiation function contains a few relatively simple parts. There is the
customization of the return value type, static type checking, building the
return value type using the helper, and creating and launching the composed
operation object. The [*`echo_op`] object does most of the work here, and has
a somewhat non-trivial structure. This structure is necessary to meet the
stringent requirements of composed operations (described in more detail in
the __Asio__ documentation). We will touch on these requirements without
explaining them in depth.
First we will create boilerplate which is present in all composed operations
written in this style:
[core_sample_echo_op_3]
We have the common boilerplate for a composed operation and now we just need
to implement the function call operator. Our strategy is to make our composed
object meet the requirements of a completion handler by being copyable (also
movable), and by providing the function call operator with the correct
signature. Rather than using `std::bind` or `boost::bind`, which destroys
the type information and therefore breaks the allocation and invocation
hooks, we will simply pass `std::move(*this)` as the completion handler
parameter for any operations that we initiate. For the move to work correctly,
care must be taken to ensure that no access to data members are made after the
move takes place. Here is the implementation of the function call operator for
this echo operation:
[core_sample_echo_op_4]
```
// echo_op is callable with the signature void(error_code, bytes_transferred),
// allowing `*this` to be used as both a ReadHandler and a WriteHandler.
//
```
A complete, runnable version of this example may be found in the examples
directory.
[endsect]

View File

@ -1,268 +0,0 @@
[/
Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
Distributed under the Boost Software License, Version 1.0. (See accompanying
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:tutorial Writing Composed Operations]
To illustrate the usage of the asynchronous helpers in the core section of
this library, we will develop a simple 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.
First we define the input parameters and results, then declare our initiation
function. For our echo operation the only inputs are the stream and the
completion token. The output is the error code which is usually included in
all completion handler signatures.
```
#include <beast/core.hpp>
#include <boost/asio.hpp>
#include <cstddef>
#include <memory>
#include <utility>
// Read a line and echo it back
//
template<class AsyncStream, class CompletionToken>
beast::async_return_type<CompletionToken, void(beast::error_code)>
async_echo(AsyncStream& stream, CompletionToken&& token)
```
Now that we have a declaration, we will define the body of the function. We
want to achieve the following goals: perform static type checking on the input
parameters, set up the return value as per __N3747__, and launch the composed
operation by constructing the object and invoking it.
```
template<class AsyncStream, class Handler>
class echo_op; // This is our composed operation implementation
// Read a line and echo it back
//
template<class AsyncStream, class CompletionToken>
beast::async_return_type<CompletionToken, void(beast::error_code)>
async_echo(AsyncStream& stream, CompletionToken&& token)
{
// Make sure stream meets the requirements. We use static_assert
// to cause a friendly message instead of an error novel.
//
static_assert(beast::is_async_stream<AsyncStream>::value,
"AsyncStream requirements not met");
// This helper manages some of the handler's lifetime and
// uses the result and handler specializations associated with
// the completion token to help customize the return value.
//
beast::async_completion<CompletionToken, void(beast::error_code)> 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.
//
echo_op<AsyncStream, beast::handler_type<CompletionToken, void(beast::error_code)>>{
stream, init.completion_handler}(beast::error_code{}, 0);
// This hook lets the caller see a return value when appropriate.
// For example this might return std::future<error_code> if
// CompletionToken is boost::asio::use_future, or this might
// return an error code if CompletionToken specifies a coroutine.
//
return init.result.get();
}
```
The initiation function contains a few relatively simple parts. There is the
customization of the return value type, static type checking, building the
return value type using the helper, and creating and launching the composed
operation object. The [*`echo_op`] object does most of the work here, and has
a somewhat non-trivial structure. This structure is necessary to meet the
stringent requirements of composed operations (described in more detail in
the __Asio__ documentation). We will touch on these requirements without
explaining them in depth.
First we will create boilerplate which is present in all composed operations
written in this style:
```
// This composed operation reads a line of input and echoes it back.
//
template<class AsyncStream, class Handler>
class echo_op
{
// This holds all of the state information required by the operation.
struct state
{
// The stream to read and write to
AsyncStream& stream;
// Indicates what step in the operation's state machine
// to perform next, starting from zero.
int step = 0;
// The buffer used to hold the input and output data.
// Note that we use a custom allocator for performance,
// this allows the implementation of the io_service to
// make efficient re-use of memory allocated by composed
// operations during continuations.
//
boost::asio::basic_streambuf<beast::handler_alloc<char, Handler>> buffer;
// handler_ptr requires that the first parameter to the
// contained object constructor is a reference to the
// managed final completion handler.
//
explicit state(Handler& handler, AsyncStream& stream_)
: stream(stream_)
, buffer((std::numeric_limits<std::size_t>::max)(),
beast::handler_alloc<char, Handler>{handler})
{
}
};
// This smart pointer container allocates our state using the
// memory allocation hooks associated with the final completion
// handler, manages the lifetime of that handler for us, and
// enforces the destroy-before-invocation requirement on memory
// allocated using the hooks.
//
beast::handler_ptr<state, Handler> p_;
public:
// Boost.Asio requires that handlers are CopyConstructible.
// In some cases, it takes advantage of handlers that are
// MoveConstructible. This operation supports both.
//
echo_op(echo_op&&) = default;
echo_op(echo_op const&) = default;
// The constructor simply creates our state variables in
// the smart pointer container.
//
template<class DeducedHandler, class... Args>
echo_op(AsyncStream& stream, DeducedHandler&& handler)
: p_(std::forward<DeducedHandler>(handler), stream)
{
}
// Determines if the next asynchronous operation represents a
// continuation of the asynchronous flow of control associated
// with the final handler. If we are past step one, it means
// we have performed an asynchronous operation therefore any
// subsequent operation would represent a continuation.
// Otherwise, we propagate the handler's associated value of
// is_continuation. Getting this right means the implementation
// may schedule the invokation of the invoked functions more
// efficiently.
//
friend bool asio_handler_is_continuation(echo_op* op)
{
// This next call is structured to permit argument
// dependent lookup to take effect.
using boost::asio::asio_handler_is_continuation;
// Always use std::addressof to pass the pointer to the handler,
// otherwise an unwanted overload of operator& may be called instead.
return op->p_->step > 1 ||
asio_handler_is_continuation(std::addressof(op->p_.handler()));
}
// 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, echo_op* op)
{
using boost::asio::asio_handler_allocate;
return asio_handler_allocate(size, std::addressof(op->p_.handler()));
}
friend void asio_handler_deallocate(void* p, std::size_t size, echo_op* op)
{
using boost::asio::asio_handler_deallocate;
return asio_handler_deallocate(p, size, std::addressof(op->p_.handler()));
}
template<class Function>
friend void asio_handler_invoke(Function&& f, echo_op* op)
{
using boost::asio::asio_handler_invoke;
return asio_handler_invoke(f, std::addressof(op->p_.handler()));
}
// 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);
};
```
We have the common boilerplate for a composed operation and now we just need
to implement the function call operator. Our strategy is to make our composed
object meet the requirements of a completion handler by being copyable (also
movable), and by providing the function call operator with the correct
signature. Rather than using `std::bind` or `boost::bind`, which destroys
the type information and therefore breaks the allocation and invocation
hooks, we will simply pass `std::move(*this)` as the completion handler
parameter for any operations that we initiate. For the move to work correctly,
care must be taken to ensure that no access to data members are made after the
move takes place. Here is the implementation of the function call operator for
this echo operation:
```
// We are callable with the signature void(error_code, bytes_transferred),
// allowing `*this` to be used as both a ReadHandler and a WriteHandler.
//
template<class AsyncStream, class Handler>
void echo_op<AsyncStream, Handler>::
operator()(beast::error_code ec, std::size_t bytes_transferred)
{
// Store a reference to our state. The address of the state won't
// change, and this solves the problem where dereferencing the
// data member is undefined after a move.
auto& p = *p_;
// Now perform the next step in the state machine
switch(ec ? 2 : p.step)
{
// initial entry
case 0:
// read up to the first newline
p.step = 1;
return boost::asio::async_read_until(p.stream, p.buffer, "\n", std::move(*this));
case 1:
// write everything back
p.step = 2;
// async_read_until could have read past the newline,
// use buffer_prefix to make sure we only send one line
return boost::asio::async_write(p.stream,
beast::buffer_prefix(bytes_transferred, p.buffer.data()), std::move(*this));
case 2:
p.buffer.consume(bytes_transferred);
break;
}
// Invoke the final handler. If we wanted to pass any arguments
// which come from our state, they would have to be moved to the
// stack first, since the `handler_ptr` guarantees that the state
// is destroyed before the handler is invoked.
//
p_.invoke(ec);
return;
}
```
A complete, runnable version of this example may be found in the examples
directory.
[endsect]

View File

@ -7,20 +7,6 @@
[section:http Using HTTP]
[block '''
<informaltable frame="all"><tgroup cols="1"><colspec colname="a"/><tbody><row><entry valign="top"><simplelist>
<member><link linkend="beast.http.primer">HTTP Primer</link></member>
<member><link linkend="beast.http.message">Message Containers</link></member>
<member><link linkend="beast.http.streams">Message Stream Operations</link></member>
<member><link linkend="beast.http.serializer_streams">Serializer Stream Operations</link></member>
<member><link linkend="beast.http.parser_streams">Parser Stream Operations</link></member>
<member><link linkend="beast.http.serializer_buffers">Buffer-Oriented Serializing</link></member>
<member><link linkend="beast.http.parser_buffers">Buffer-Oriented Parsing</link></member>
<member><link linkend="beast.http.custom_parsers">Custom Parsers</link></member>
<member><link linkend="beast.http.custom_body">Custom Body Types</link></member>
</simplelist></entry></row></tbody></tgroup></informaltable>
''']
This library offers programmers simple and performant models of HTTP messages
and their associated operations including synchronous, asynchronous, and
buffer-oriented parsing and serialization of messages in the HTTP/1 wire
@ -44,7 +30,7 @@ format using __Asio__. Specifically, the library provides:
[link beast.ref.http__async_read_header `async_read_header`], and
[link beast.ref.http__async_read_some `async_read_some`]
read HTTP/1 message data from a
[link beast.ref.streams stream].
[link beast.concept.streams stream].
]
][
[Stream Writing]
@ -57,7 +43,7 @@ format using __Asio__. Specifically, the library provides:
[link beast.ref.http__async_write_header `async_write_header`], and
[link beast.ref.http__async_write_some `async_write_some`]
write HTTP/1 message data to a
[link beast.ref.streams stream].
[link beast.concept.streams stream].
]
][
[Serialization]
@ -76,24 +62,24 @@ format using __Asio__. Specifically, the library provides:
]
[note
The following documentation assumes some familiarity with __Asio__ and
the HTTP protocol specification described in __rfc7230__. Sample code
and identifiers mentioned in the HTTP documentation sections are
written as if these declarations are in effect:
This documentation assumes some familiarity with __Asio__ and
the HTTP protocol specification described in __rfc7230__. Sample
code and identifiers mentioned in this section is written as if
these declarations are in effect:
```
#include <beast/http.hpp>
using namespace beast::http;
```
]
[include 4_1_primer.qbk]
[include 4_2_message.qbk]
[include 4_3_streams.qbk]
[include 4_4_serializer_streams.qbk]
[include 4_5_parser_streams.qbk]
[include 4_6_serializer_buffers.qbk]
[include 4_7_parser_buffers.qbk]
[include 4_8_custom_parsers.qbk]
[include 4_9_custom_body.qbk]
[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]
[endsect]

View File

@ -5,7 +5,7 @@
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:primer HTTP Primer]
[section:primer Protocol Primer]
The HTTP protocol defines the
[@https://tools.ietf.org/html/rfc7230#section-2.1 client and server roles]:
@ -85,7 +85,7 @@ field informs the remote host of the size of the body which follows.
This response has a
[@https://tools.ietf.org/html/rfc7231#section-6 200 status code]
meaning the operation requested completed successfully. The obsolete
reason phrase is "OK". It has specifies HTTP version 1.1, and contains
reason phrase is "OK". It specifies HTTP version 1.1, and contains
a body 13 octets in size with the text "Hello, world!".
]]
]

View File

@ -0,0 +1,106 @@
[/
Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
Distributed under the Boost Software License, Version 1.0. (See accompanying
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:serializer_streams Serializer Stream Operations]
Algorithms for sending entire messages to streams are intended for light
duty use-cases such as simple clients and low utilization servers.
Sophisticated algorithms will need to do more:
* Send the message header first.
* Send a message incrementally: bounded work in each I/O cycle.
* Use a custom chunk decorator or allocator when sending messages.
* Use a series of caller-provided buffers to represent the body.
All of these operations require callers to manage the lifetime of state
information associated with the operation, by constructing a __serializer__
object with the message to be sent. The serializer type has this declaration:
```
template<
bool isRequest,
class Body,
class Fields,
class ChunkDecorator = no_chunk_decorator,
class Allocator = std::allocator<char>
>
class serializer;
```
The choices for template types must match the message passed on construction.
This code creates an HTTP response and the corresponding serializer:
```
response<string_body> res;
...
serializer<false, string_body, fields> sr{res};
```
The convenience function
[link beast.ref.http__make_serializer `make_serializer`]
is provided to avoid repetition of template argument types. The declaration
for `sr` in the code above may be written as:
```
...
auto sr = make_serializer(res);
```
The stream operations which work on serializers are:
[table Serializer Stream Operations
[[Name][Description]]
[[
[link beast.ref.http__write.overload1 [*write]]
][
Send everything in a __serializer__ to a __SyncWriteStream__.
]]
[[
[link beast.ref.http__async_write.overload1 [*async_write]]
][
Send everything in a __serializer__ asynchronously to an __AsyncWriteStream__.
]]
[[
[link beast.ref.http__write_header.overload1 [*write_header]]
][
Send only the header from a __serializer__ to a __SyncWriteStream__.
]]
[[
[link beast.ref.http__async_write_header [*async_write_header]]
][
Send only the header from a __serializer__ asynchronously to an __AsyncWriteStream__.
]]
[[
[link beast.ref.http__write_some.overload1 [*write_some]]
][
Send some __serializer__ buffer data to a __SyncWriteStream__.
]]
[[
[link beast.ref.http__async_write_some [*async_write_some]]
][
Send some __serializer__ buffer data asynchronously to an __AsyncWriteStream__.
]]
]
Here is an example of using a serializer to send a message on a stream
synchronously. This performs the same operation as calling `write(stream, m)`:
```
template<class SyncWriteStream, bool isRequest, class Body, class Fields>
void send(SyncWriteStream& stream, message<isRequest, Body, Fields> const& m)
{
static_assert(is_sync_write_stream<SyncWriteStream>::value,
"SyncWriteStream requirements not met");
serializer<isRequest, Body, Fields> sr{m};
do
{
write_some(stream, sr);
}
while(! sr.is_done());
}
```
[endsect]

147
doc/4_05_parser_streams.qbk Normal file
View File

@ -0,0 +1,147 @@
[/
Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
Distributed under the Boost Software License, Version 1.0. (See accompanying
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:parser_streams Parser Stream Operations]
Algorithms for receiving entire messages from streams are helpful for simple
use-cases. Sophisticated algorithms will need to do more:
* Receive the message header first.
* Receive a message incrementally: bounded work in each I/O cycle.
* Receive an arbitrarily-sized body using a fixed-size buffer.
* Defer the commitment to a __Body__ type until after reading the header.
All of these operations require callers to manage the lifetime of state
information associated with the operation, by constructing a class derived
from __basic_parser__. Beast comes with two instances of parsers, and user
defined types deriving from the basic parser are possible:
[table Parser Implementations
[[Name][Description]]
[[
__parser__
][
```
/// An HTTP/1 parser for producing a message.
template<
bool isRequest, // `true` to parse an HTTP request
class Body, // The Body type for the resulting message
class Fields> // The type of container representing the fields
class parser
: public basic_parser<...>;
```
]]
[[
__header_parser__
][
```
/// An HTTP/1 parser for producing a header.
template<
bool isRequest, // `true` to parse an HTTP request
class Fields> // The type of container representing the fields
class header_parser
: public basic_parser<...>;
```
]]
[[
[link beast.ref.http__request_parser `request_parser`]
][
```
/// An HTTP/1 parser for producing a request message.
template<class Body, class Fields = fields>
using request_parser = parser<true, Body, Fields>;
```
]]
[[
[link beast.ref.http__response_parser `response_parser`]
][
```
/// An HTTP/1 parser for producing a response message.
template<class Body, class Fields = fields>
using response_parser = parser<false, Body, Fields>;
```
]]
]
[note
The __basic_parser__ and classes derived from it handle octet streams
serialized in the HTTP/1 format described in __rfc7230__.
]
The stream operations which work on parsers are:
[table Parser Stream Operations
[[Name][Description]]
[[
[link beast.ref.http__read.overload1 [*read]]
][
Read everything into a parser from a __SyncWriteStream__.
]]
[[
[link beast.ref.http__async_read.overload1 [*async_read]]
][
Read everything into a parser asynchronously from an __AsyncWriteStream__.
]]
[[
[link beast.ref.http__read_header.overload1 [*read_header]]
][
Read only the header octets into a parser from a __SyncWriteStream__.
]]
[[
[link beast.ref.http__async_read_header [*async_read_header]]
][
Read only the header octets into a parser asynchronously from an __AsyncWriteStream__.
]]
[[
[link beast.ref.http__read_some.overload1 [*read_some]]
][
Read some octets into a parser from a __SyncReadStream__.
]]
[[
[link beast.ref.http__async_read_some [*async_read_some]]
][
Read some octets into a parser asynchronously from an __AsyncWriteStream__.
]]
]
As with the stream parse algorithms which operate on entire messages, stream
operations for parsers require a passed-in __DynamicBuffer__ which persists
between calls to hold unused octets from the stream. The basic parser
implementation is optimized for the case where this dynamic buffer stores
its input sequence in a single contiguous memory buffer. It is advised to
use an instance of __flat_buffer__ for this purpose, although a user defined
instance of __DynamicBuffer__ which produces input sequences of length one
is also suitable.
The provided parsers use a "captive object" model, acting as container for
the __message__ or __header__ produced as a result of parsing. The caller
accesses the contained object, and depending on the types used to instantiate
the parser, it may be possible to acquire ownership of the header or message
captive object and destroy the parser. In this example we read an HTTP
response with a string body using a parser, then print the response:
```
template<class SyncReadStream>
void print_response(SyncReadStream& stream)
{
static_assert(is_sync_read_stream<SyncReadStream>::value,
"SyncReadStream requirements not met");
// Declare a parser for an HTTP response
response_parser<string_body> parser;
// Read the entire message
read(stream, parser);
// Now print the message
std::cout << parser.get() << std::endl;
}
```
[endsect]

View File

@ -0,0 +1,63 @@
[/
Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
Distributed under the Boost Software License, Version 1.0. (See accompanying
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:parser_buffers Buffer-Oriented Parsing]
In extreme cases, users may wish to create an instance of __parser__,
__header_parser__, or a user-defined type derived from __basic_parser__ and
invoke its methods directly instead of using the provided stream algorithms.
This could be useful for implementing algorithms on streams whose interface
does not conform to any __Stream__. For example, a
[@http://zeromq.org/ *ZeroMQ* socket].
The basic parser interface is interactive; the caller invokes the function
[link beast.ref.http__basic_parser.put `basic_parser::put`]
repeatedly with buffers until an error occurs or the parsing is done. The
function
[link beast.ref.http__basic_parser.put_eof `basic_parser::put_eof`]
Is used when the caller knows that there will never be more data (for example,
if the underlying connection is closed),
[heading Parser Options]
The parser provides two options which may be set before parsing begins:
[table Parser Options
[[Name][Default][Description]]
[[
[link beast.ref.http__basic_parser.eager.overload2 `eager`]
][
`false`
][
Normally the parser returns after successfully parsing a structured
element (header, chunk header, or chunk body) even if there are octets
remaining in the input. This is necessary when attempting to parse the
header first, or when the caller wants to inspect information which may
be invalidated by subsequent parsing, such as a chunk extension. The
`eager` option controls whether the parser keeps going after parsing
structured element if there are octets remaining in the buffer and no
error occurs. This option is automatically set or cleared during certain
stream operations to improve performance with no change in functionality.
]]
[[
[link beast.ref.http__basic_parser.skip.overload2 `skip`]
][
`false`
][
This option controls whether or not the parser expects to see an HTTP
body, regardless of the presence or absence of certain fields such as
Content-Length or a chunked Transfer-Encoding. Depending on the request,
some responses do not carry a body. For example, a 200 response to a
[@https://tools.ietf.org/html/rfc7231#section-4.3.6 CONNECT] request
from a tunneling proxy, or a response to a
[@https://tools.ietf.org/html/rfc7231#section-4.3.2 HEAD] request.
In these cases, callers may use this function inform the parser that
no body is expected. The parser will consider the message complete
after the header has been received.
]]
]
[endsect]

View File

@ -1,306 +0,0 @@
[/
Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
Distributed under the Boost Software License, Version 1.0. (See accompanying
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:serializer_streams Serializer Stream Operations]
Algorithms for sending entire messages to streams are intended for light
duty use-cases such as simple clients and low utilization servers.
Sophisticated algorithms will need to do more:
* Send the message header first.
* Send a message incrementally: bounded work in each I/O cycle.
* Use a custom chunk decorator or allocator when sending messages.
* Use a series of caller-provided buffers to represent the body.
All of these operations require callers to manage the lifetime of state
information associated with the operation, by constructing a __serializer__
object with the message to be sent. The serializer type has this declaration:
```
template<
bool isRequest,
class Body,
class Fields,
class ChunkDecorator = no_chunk_decorator,
class Allocator = std::allocator<char>
>
class serializer;
```
The choices for template types must match the message passed on construction.
This code creates an HTTP response and the corresponding serializer:
```
response<string_body> res;
...
serializer<false, string_body, fields> sr{res};
```
The convenience function
[link beast.ref.http__make_serializer `make_serializer`]
is provided to avoid repetition of template argument types. The declaration
for `sr` in the code above may be written as:
```
...
auto sr = make_serializer(res);
```
The stream operations which work on serializers are:
[table Serializer Stream Operations
[[Name][Description]]
[[
[link beast.ref.http__write_some.overload1 [*write_some]]
][
Send some __serializer__ buffer data to a __SyncWriteStream__.
]]
[[
[link beast.ref.http__async_write_some [*async_write_some]]
][
Send some __serializer__ buffer data asynchronously to an __AsyncWriteStream__.
]]
[[
[link beast.ref.http__write_header.overload1 [*write_header]]
][
Send only the header from a __serializer__ to a __SyncWriteStream__.
]]
[[
[link beast.ref.http__async_write_header [*async_write_header]]
][
Send only the header from a __serializer__ asynchronously to an __AsyncWriteStream__.
]]
[[
[link beast.ref.http__write.overload1 [*write]]
][
Send everything in a __serializer__ to a __SyncWriteStream__.
]]
[[
[link beast.ref.http__async_write.overload1 [*async_write]]
][
Send everything in a __serializer__ asynchronously to an __AsyncWriteStream__.
]]
]
Here is an example of using a serializer to send a message on a stream
synchronously. This performs the same operation as calling `write(stream, m)`:
```
template<class SyncWriteStream, bool isRequest, class Body, class Fields>
void send(SyncWriteStream& stream, message<isRequest, Body, Fields> const& m)
{
static_assert(is_sync_write_stream<SyncWriteStream>::value,
"SyncWriteStream requirements not met");
serializer<isRequest, Body, Fields> sr{m};
do
{
write_some(stream, sr);
}
while(! sr.is_done());
}
```
[heading Example: Expect 100-continue]
The Expect field with the value "100-continue" in a request is special. It
indicates that the after sending the message header, a client desires an
immediate informational response before sending the the message body, which
presumably may be expensive to compute or large. This behavior is described in
[@https://tools.ietf.org/html/rfc7231#section-5.1.1 rfc7231 section 5.1.1].
Invoking the 100-continue behavior is implemented easily in a client by
constructing a __serializer__ to send the header first, then receiving
the server response, and finally conditionally send the body using the same
serializer instance. A synchronous, simplified version (no timeout) of
this client action looks like this:
```
/** Send a request with Expect: 100-continue
This function will send a request with the Expect: 100-continue
field by first sending the header, then waiting for a successful
response from the server before continuing to send the body. If
a non-successful server response is received, the function
returns immediately.
@param stream The remote HTTP server stream.
@param buffer The buffer used for reading.
@param req The request to send. This function modifies the object:
the Expect header field is inserted into the message if it does
not already exist, and set to "100-continue".
@param ec Set to the error, if any occurred.
*/
template<
class SyncStream,
class DynamicBuffer,
class Body, class Fields>
void
send_expect_100_continue(
SyncStream& stream,
DynamicBuffer& buffer,
request<Body, Fields>& req)
{
static_assert(is_sync_stream<SyncStream>::value,
"SyncStream requirements not met");
static_assert(is_dynamic_buffer<DynamicBuffer>::value,
"DynamicBuffer requirements not met");
// Insert or replace the Expect field
req.fields.replace("Expect", "100-continue");
// Create the serializer
auto sr = make_serializer(req);
// Send just the header
write_header(stream, sr);
// Read the response from the server.
// A robust client could set a timeout here.
{
response<string_body> res;
read(stream, buffer, res);
if(res.result() != status::continue_)
{
// The server indicated that it will not
// accept the request, so skip sending the body.
return;
}
}
// Server is OK with the request, send the body
write(stream, sr);
}
```
[heading Example: Using Manual Buffers]
Sometimes it is necessary to send a message whose body is not conveniently
described by a single container. For example, when implementing an HTTP relay
function a robust implementation needs to present body buffers individually
as they become available from the downstream host. These buffers should be
fixed in size, otherwise creating the unnecessary and inefficient burden of
reading the complete message body before forwarding it to the upstream host.
To enable these use-cases, the body type __buffer_body__ is provided. This
body uses a caller-provided pointer and size instead of an owned container.
To use this body, instantiate an instance of the serializer and fill in
the pointer and size fields before calling a stream write function.
This example reads from a child process and sends the output back in an
HTTP response. The output of the process is sent as it becomes available:
```
/** Send the output of a child process as an HTTP response.
The output of the child process comes from a @b SyncReadStream. Data
will be sent continuously as it is produced, without the requirement
that the entire process output is buffered before being sent. The
response will use the chunked transfer encoding.
@param input A stream to read the child process output from.
@param output A stream to write the HTTP response to.
@param ec Set to the error, if any occurred.
*/
template<
class SyncReadStream,
class SyncWriteStream>
void
send_cgi_response(
SyncReadStream& input,
SyncWriteStream& output,
error_code& ec)
{
static_assert(is_sync_read_stream<SyncReadStream>::value,
"SyncReadStream requirements not met");
static_assert(is_sync_write_stream<SyncWriteStream>::value,
"SyncWriteStream requirements not met");
using boost::asio::buffer_cast;
using boost::asio::buffer_size;
// Set up the response. We use the buffer_body type,
// allowing serialization to use manually provided buffers.
message<false, buffer_body, fields> res;
res.result(status::ok);
res.version = 11;
res.fields.insert("Server", "Beast");
res.fields.insert("Transfer-Encoding", "chunked");
// No data yet, but we set more = true to indicate
// that it might be coming later. Otherwise the
// serializer::is_done would return true right after
// sending the header.
res.body.data = nullptr;
res.body.more = true;
// Create the serializer. We set the split option to
// produce the header immediately without also trying
// to acquire buffers from the body (which would return
// the error http::need_buffer because we set `data`
// to `nullptr` above).
auto sr = make_serializer(res);
// Send the header immediately.
write_header(output, sr, ec);
if(ec)
return;
// Alternate between reading from the child process
// and sending all the process output until there
// is no more output.
do
{
// Read a buffer from the child process
char buffer[2048];
auto bytes_transferred = input.read_some(
boost::asio::buffer(buffer, sizeof(buffer)), ec);
if(ec == boost::asio::error::eof)
{
ec = {};
// `nullptr` indicates there is no buffer
res.body.data = nullptr;
// `false` means no more data is coming
res.body.more = false;
}
else
{
if(ec)
return;
// Point to our buffer with the bytes that
// we received, and indicate that there may
// be some more data coming
res.body.data = buffer;
res.body.size = bytes_transferred;
res.body.more = true;
}
// Write everything in the body buffer
write(output, sr, ec);
// This error is returned by body_buffer during
// serialization when it is done sending the data
// provided and needs another buffer.
if(ec == error::need_buffer)
{
ec = {};
continue;
}
if(ec)
return;
}
while(! sr.is_done());
}
```
[endsect]

View File

@ -1,420 +0,0 @@
[/
Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
Distributed under the Boost Software License, Version 1.0. (See accompanying
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:parser_streams Parser Stream Operations]
Algorithms for receiving entire messages from streams are helpful for simple
use-cases. Sophisticated algorithms will need to do more:
* Receive the message header first.
* Receive a message incrementally: bounded work in each I/O cycle.
* Receive an arbitrarily-sized body using a fixed-size buffer.
* Defer the commitment to a __Body__ type until after reading the header.
All of these operations require callers to manage the lifetime of state
information associated with the operation, by constructing a class derived
from __basic_parser__. Beast comes with two instances of parsers, and user
defined types deriving from the basic parser are possible:
[table Parser Implementations
[[Name][Description]]
[[
__parser__
][
```
/// An HTTP/1 parser for producing a message.
template<
bool isRequest, // `true` to parse an HTTP request
class Body, // The Body type for the resulting message
class Fields> // The type of container representing the fields
class parser
: public basic_parser<...>;
```
]]
[[
__header_parser__
][
```
/// An HTTP/1 parser for producing a header.
template<
bool isRequest, // `true` to parse an HTTP request
class Fields> // The type of container representing the fields
class header_parser
: public basic_parser<...>;
```
]]
[[
[link beast.ref.http__request_parser `request_parser`]
][
```
/// An HTTP/1 parser for producing a request message.
template<class Body, class Fields = fields>
using request_parser = parser<true, Body, Fields>;
```
]]
[[
[link beast.ref.http__response_parser `response_parser`]
][
```
/// An HTTP/1 parser for producing a response message.
template<class Body, class Fields = fields>
using response_parser = parser<false, Body, Fields>;
```
]]
]
[note
The __basic_parser__ and classes derived from it handle octet streams
serialized in the HTTP/1 format described in __rfc7230__.
]
The stream operations which work on parsers are:
[table Parser Stream Operations
[[Name][Description]]
[[
[link beast.ref.http__read.overload1 [*read]]
][
Read everything into a parser from a __SyncWriteStream__.
]]
[[
[link beast.ref.http__async_read.overload1 [*async_read]]
][
Read everything into a parser asynchronously from an __AsyncWriteStream__.
]]
[[
[link beast.ref.http__read_header.overload1 [*read_header]]
][
Read only the header octets into a parser from a __SyncWriteStream__.
]]
[[
[link beast.ref.http__async_read_header [*async_read_header]]
][
Read only the header octets into a parser asynchronously from an __AsyncWriteStream__.
]]
[[
[link beast.ref.http__read_some.overload1 [*read_some]]
][
Read some octets into a parser from a __SyncReadStream__.
]]
[[
[link beast.ref.http__async_read_some [*async_read_some]]
][
Read some octets into a parser asynchronously from an __AsyncWriteStream__.
]]
]
As with the stream parse algorithms which operate on entire messages, stream
operations for parsers require a passed-in __DynamicBuffer__ which persists
between calls to hold unused octets from the stream. The basic parser
implementation is optimized for the case where this dynamic buffer stores
its input sequence in a single contiguous memory buffer. It is advised to
use an instance of __flat_buffer__ for this purpose, although a user defined
instance of __DynamicBuffer__ which produces input sequences of length one
is also suitable.
The provided parsers use a "captive object" model, acting as container for
the __message__ or __header__ produced as a result of parsing. The caller
accesses the contained object, and depending on the types used to instantiate
the parser, it may be possible to acquire ownership of the header or message
captive object and destroy the parser. In this example we read an HTTP
response with a string body using a parser, then print the response:
```
template<class SyncReadStream>
void print_response(SyncReadStream& stream)
{
static_assert(is_sync_read_stream<SyncReadStream>::value,
"SyncReadStream requirements not met");
// Declare a parser for an HTTP response
response_parser<string_body> parser;
// Read the entire message
read(stream, parser);
// Now print the message
std::cout << parser.get() << std::endl;
}
```
[heading Example: 100-continue]
The Expect field with the value "100-continue" in a request is special. It
indicates that the after sending the message header, a client desires an
immediate informational response before sending the the message body, which
presumably may be expensive to compute or large. This behavior is described in
[@https://tools.ietf.org/html/rfc7231#section-5.1.1 rfc7231 section 5.1.1].
Handling the Expect field can be implemented easily in a server by constructing
a __parser__ to read the header first, then send an informational HTTP
response, and finally read the body using the same parser instance. A
synchronous version of this server action looks like this:
```
/** Receive a request, handling Expect: 100-continue if present.
This function will read a request from the specified stream.
If the request contains the Expect: 100-continue field, a
status response will be delivered.
@param stream The remote HTTP client stream.
@param buffer The buffer used for reading.
@param ec Set to the error, if any occurred.
*/
template<
class SyncStream,
class DynamicBuffer>
void
receive_expect_100_continue(
SyncStream& stream,
DynamicBuffer& buffer,
error_code& ec)
{
static_assert(is_sync_stream<SyncStream>::value,
"SyncStream requirements not met");
static_assert(is_dynamic_buffer<DynamicBuffer>::value,
"DynamicBuffer requirements not met");
// Declare a parser for a request with a string body
request_parser<string_body> parser;
// Read the header
read_header(stream, buffer, parser, ec);
if(ec)
return;
// Check for the Expect field value
if(parser.get().fields["Expect"] == "100-continue")
{
// send 100 response
response<empty_body> res;
res.version = 11;
res.result(status::continue_);
res.fields.insert("Server", "test");
write(stream, res, ec);
if(ec)
return;
}
// Read the rest of the message.
//
// We use parser.base() to return a basic_parser&, to avoid an
// ambiguous function error (from boost::asio::read). Another
// solution is to qualify the call, e.g. `beast::http::read`
//
read(stream, buffer, parser.base(), ec);
}
```
[heading Example: HEAD request (Client)]
```
/** Send a HEAD request for a resource.
This function submits a HEAD request for the specified resource
and returns the response.
@param res The response. This is an output parameter.
@param stream The synchronous stream to use.
@param buffer The buffer to use.
@param target The request target.
@param ec Set to the error, if any occurred.
@throws std::invalid_argument if target is empty.
*/
template<
class SyncStream,
class DynamicBuffer
>
response<empty_body>
do_head_request(
SyncStream& stream,
DynamicBuffer& buffer,
string_view target,
error_code& ec)
{
// Do some type checking to be a good citizen
static_assert(is_sync_stream<SyncStream>::value,
"SyncStream requirements not met");
static_assert(is_dynamic_buffer<DynamicBuffer>::value,
"DynamicBuffer requirments not met");
// The interfaces we are using are low level and do not
// perform any checking of arguments; so we do it here.
if(target.empty())
throw std::invalid_argument("target may not be empty");
// Build the HEAD request for the target
request<empty_body> req;
req.version = 11;
req.method(verb::head);
req.target(target);
req.fields.insert("User-Agent", "test");
// A client MUST send a Host header field in all HTTP/1.1 request messages.
// https://tools.ietf.org/html/rfc7230#section-5.4
req.fields.insert("Host", "localhost");
// Now send it
write(stream, req, ec);
if(ec)
return {};
// Create a parser to read the response.
// Responses to HEAD requests MUST NOT include
// a body, so we use the `empty_body` type and
// only attempt to read the header.
parser<false, empty_body> p;
read_header(stream, buffer, p, ec);
if(ec)
return {};
// Transfer ownership of the response to the caller.
return p.release();
}
```
[heading Example: 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:
```
/** Relay an HTTP message.
This function efficiently relays an HTTP message from a downstream
client to an upstream server, or from an upstream server to a
downstream client. After the message header is read from the input,
a user provided transformation function is invoked which may change
the contents of the header before forwarding to the output. This may
be used to adjust fields such as Server, or proxy fields.
@param output The stream to write to.
@param input The stream to read from.
@param buffer The buffer to use for the input.
@param transform The header transformation to apply. The function will
be called with this signature:
@code
void transform(
header<isRequest, Fields>&, // The header to transform
error_code&); // Set to the error, if any
@endcode
@param ec Set to the error if any occurred.
@tparam isRequest `true` to relay a request.
@tparam Fields The type of fields to use for the message.
*/
template<
bool isRequest,
class Fields = fields,
class SyncWriteStream,
class SyncReadStream,
class DynamicBuffer,
class Transform>
void
relay(
SyncWriteStream& output,
SyncReadStream& input,
DynamicBuffer& buffer,
error_code& ec,
Transform&& transform)
{
static_assert(is_sync_write_stream<SyncWriteStream>::value,
"SyncWriteStream requirements not met");
static_assert(is_sync_read_stream<SyncReadStream>::value,
"SyncReadStream requirements not met");
// A small buffer for relaying the body piece by piece
char buf[2048];
// Create a parser with a buffer body to read from the input.
parser<isRequest, buffer_body, Fields> p;
// Create a serializer from the message contained in the parser.
serializer<isRequest, buffer_body, Fields> sr{p.get()};
// Read just the header from the input
read_header(input, buffer, p, ec);
if(ec)
return;
// Apply the caller's header tranformation
// base() returns a reference to the header portion of the message.
transform(p.get().base(), ec);
if(ec)
return;
// Send the transformed message to the output
write_header(output, sr, ec);
if(ec)
return;
// Loop over the input and transfer it to the output
do
{
if(! p.is_done())
{
// Set up the body for writing into our small buffer
p.get().body.data = buf;
p.get().body.size = sizeof(buf);
// Read as much as we can
read(input, buffer, p, ec);
// This error is returned when buffer_body uses up the buffer
if(ec == error::need_buffer)
ec = {};
if(ec)
return;
// Set up the body for reading.
// This is how much was parsed:
p.get().body.size = sizeof(buf) - p.get().body.size;
p.get().body.data = buf;
p.get().body.more = ! p.is_done();
}
else
{
p.get().body.data = nullptr;
p.get().body.size = 0;
}
// Write everything in the buffer (which might be empty)
write(output, sr, ec);
// This error is returned when buffer_body uses up the buffer
if(ec == error::need_buffer)
ec = {};
if(ec)
return;
}
while(! p.is_done() && ! sr.is_done());
}
```
[endsect]

View File

@ -1,170 +0,0 @@
[/
Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
Distributed under the Boost Software License, Version 1.0. (See accompanying
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:parser_buffers Buffer-Oriented Parsing]
In extreme cases, users may wish to create an instance of __parser__,
__header_parser__, or a user-defined type derived from __basic_parser__ and
invoke its methods directly instead of using the provided stream algorithms.
This could be useful for implementing algorithms on streams whose interface
does not conform to any __Stream__. For example, a
[@http://zeromq.org/ *ZeroMQ* socket].
The basic parser interface is interactive; the caller invokes the function
[link beast.ref.http__basic_parser.put `basic_parser::put`]
repeatedly with buffers until an error occurs or the parsing is done. The
function
[link beast.ref.http__basic_parser.put_eof `basic_parser::put_eof`]
Is used when the caller knows that there will never be more data (for example,
if the underlying connection is closed),
[heading Parser Options]
The parser provides two options which may be set before parsing begins:
[table Parser Options
[[Name][Default][Description]]
[[
[link beast.ref.http__basic_parser.eager.overload2 `eager`]
][
`false`
][
Normally the parser returns after successfully parsing a structured
element (header, chunk header, or chunk body) even if there are octets
remaining in the input. This is necessary when attempting to parse the
header first, or when the caller wants to inspect information which may
be invalidated by subsequent parsing, such as a chunk extension. The
`eager` option controls whether the parser keeps going after parsing
structured element if there are octets remaining in the buffer and no
error occurs. This option is automatically set or cleared during certain
stream operations to improve performance with no change in functionality.
]]
[[
[link beast.ref.http__basic_parser.skip.overload2 `skip`]
][
`false`
][
This option controls whether or not the parser expects to see an HTTP
body, regardless of the presence or absence of certain fields such as
Content-Length or a chunked Transfer-Encoding. Depending on the request,
some responses do not carry a body. For example, a 200 response to a
[@https://tools.ietf.org/html/rfc7231#section-4.3.6 CONNECT] request
from a tunneling proxy, or a response to a
[@https://tools.ietf.org/html/rfc7231#section-4.3.2 HEAD] request.
In these cases, callers may use this function inform the parser that
no body is expected. The parser will consider the message complete
after the header has been received.
]]
]
[heading Example: Parsing from a std::istream]
The standard library provides the type `std::istream` for performing high
level operations on character streams. The variable `std::cin` is based
on this input stream. In this example, we build a stream operation which
parses an HTTP message from a `std::istream`:
```
/** Parse an HTTP/1 message from a `std::istream`.
This function attempts to parse a complete message from the stream.
@param is The `std::istream` to read from.
@param buffer The buffer to use.
@param msg The message to store the result.
@param ec Set to the error, if any occurred.
*/
template<
class Allocator,
bool isRequest,
class Body,
class Fields>
void
parse_istream(
std::istream& is,
basic_flat_buffer<Allocator>& buffer,
message<isRequest, Body, Fields>& msg,
error_code& ec)
{
// Create the message parser
parser<isRequest, Body, Fields> parser;
do
{
// Extract whatever characters are presently available in the istream
if(is.rdbuf()->in_avail() > 0)
{
// Get a mutable buffer sequence for writing
auto const mb = buffer.prepare(is.rdbuf()->in_avail());
// Now get everything we can from the istream
buffer.commit(is.readsome(
boost::asio::buffer_cast<char*>(mb),
boost::asio::buffer_size(mb)));
}
else if(buffer.size() == 0)
{
// Our buffer is empty and we need more characters,
// see if we've reached the end of file on the istream
if(! is.eof())
{
// Get a mutable buffer sequence for writing
auto const mb = buffer.prepare(1024);
// Try to get more from the istream. This might block.
is.read(
boost::asio::buffer_cast<char*>(mb),
boost::asio::buffer_size(mb));
// If an error occurs on the istream then return it to the caller.
if(is.fail() && ! is.eof())
{
// We'll just re-use io_error since std::istream has no error_code interface.
ec = make_error_code(errc::io_error);
return;
}
// Commit the characters we got to the buffer.
buffer.commit(is.gcount());
}
else
{
// Inform the parser that we've reached the end of the istream.
parser.put_eof(ec);
if(ec)
return;
break;
}
}
// Write the data to the parser
auto const bytes_used = parser.put(buffer.data(), ec);
// This error means that the parser needs additional octets.
if(ec == error::need_more)
ec = {};
if(ec)
return;
// Consume the buffer octets that were actually parsed.
buffer.consume(bytes_used);
}
while(! parser.is_done());
// Transfer ownership of the message container in the parser to the caller.
msg = parser.release();
}
```
[tip
Parsing from a `std::istream` could be implemented using an alternate
strategy: adapt the `std::istream` interface to a __SyncReadStream__.
This would allow it to work with the library's existing algorithms.
We leave this as an exercise for the reader.
]
[endsect]

157
doc/5_http_examples.qbk Normal file
View File

@ -0,0 +1,157 @@
[/
Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
Distributed under the Boost Software License, Version 1.0. (See accompanying
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section HTTP Examples]
[section Expect 100-continue (Client)]
The Expect field with the value "100-continue" in a request is special. It
indicates that the after sending the message header, a client desires an
immediate informational response before sending the the message body, which
presumably may be expensive to compute or large. This behavior is described in
[@https://tools.ietf.org/html/rfc7231#section-5.1.1 rfc7231 section 5.1.1].
Invoking the 100-continue behavior is implemented easily in a client by
constructing a __serializer__ to send the header first, then receiving
the server response, and finally conditionally send the body using the same
serializer instance. A synchronous, simplified version (no timeout) of
this client action looks like this:
[http_sample_send_expect_100_continue]
[endsect]
[section Expect 100-continue (Server)]
The Expect field with the value "100-continue" in a request is special. It
indicates that the after sending the message header, a client desires an
immediate informational response before sending the the message body, which
presumably may be expensive to compute or large. This behavior is described in
[@https://tools.ietf.org/html/rfc7231#section-5.1.1 rfc7231 section 5.1.1].
Handling the Expect field can be implemented easily in a server by constructing
a __parser__ to read the header first, then send an informational HTTP
response, and finally read the body using the same parser instance. A
synchronous version of this server action looks like this:
[http_sample_receive_expect_100_continue]
[endsect]
[section Send Child Process Output]
Sometimes it is necessary to send a message whose body is not conveniently
described by a single container. For example, when implementing an HTTP relay
function a robust implementation needs to present body buffers individually
as they become available from the downstream host. These buffers should be
fixed in size, otherwise creating the unnecessary and inefficient burden of
reading the complete message body before forwarding it to the upstream host.
To enable these use-cases, the body type __buffer_body__ is provided. This
body uses a caller-provided pointer and size instead of an owned container.
To use this body, instantiate an instance of the serializer and fill in
the pointer and size fields before calling a stream write function.
This example reads from a child process and sends the output back in an
HTTP response. The output of the process is sent as it becomes available:
[http_sample_send_cgi_response]
[endsect]
[section HEAD request (Client)]
The
[@https://tools.ietf.org/html/rfc7231#section-4.3.2 HEAD request]
method indicates to the server that the client wishes to receive the
entire header that would be delivered if the method was GET, except
that the body is omitted.
[http_sample_do_head_request]
[endsect]
[section HEAD response (Server)]
When a server receives a
[@https://tools.ietf.org/html/rfc7231#section-4.3.2 HEAD request],
the response should contain the entire header that would be delivered
if the method was GET, except that the body is omitted.
[http_sample_do_head_response]
[endsect]
[section Write To std::ostream]
The standard library provides the type `std::ostream` for performing high
level write operations on character streams. The variable `std::cout` is
based on this output stream. In this example, we build a stream operation
which serializes an HTTP message to a `std::ostream`:
[http_sample_write_ostream]
[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]
[section Read From std::istream]
The standard library provides the type `std::istream` for performing high
level read operations on character streams. The variable `std::cin` is based
on this input stream. In this example, we build a stream operation which
parses an HTTP message from a `std::istream`:
[http_sample_read_istream]
[tip
Parsing from a `std::istream` could be implemented using an alternate
strategy: adapt the `std::istream` interface to a __SyncReadStream__.
This lets all the library's existing algorithms work on `std::istream`.
We leave this as an exercise for the reader.
]
[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]
[endsect]
[endsect]

View File

@ -7,18 +7,6 @@
[section:websocket Using WebSocket]
[block '''
<informaltable frame="all"><tgroup cols="1"><colspec colname="a"/><tbody><row><entry valign="top"><simplelist>
<member><link linkend="beast.websocket.streams">Creating Streams</link></member>
<member><link linkend="beast.websocket.connect">Establishing Connections</link></member>
<member><link linkend="beast.websocket.client">Handshaking (Clients)</link></member>
<member><link linkend="beast.websocket.server">Handshaking (Servers)</link></member>
<member><link linkend="beast.websocket.messages">Send and Receive Messages</link></member>
<member><link linkend="beast.websocket.control">Control Frames</link></member>
<member><link linkend="beast.websocket.notes">Additional Notes</link></member>
</simplelist></entry></row></tbody></tgroup></informaltable>
''']
The WebSocket Protocol enables two-way communication between a client
running untrusted code in a controlled environment to a remote host that has
opted-in to communications from that code. The protocol consists of an opening
@ -39,12 +27,12 @@ Boost.Asio with a consistent asynchronous model using a modern C++ approach.
```
]
[include 5_1_streams.qbk]
[include 5_2_connect.qbk]
[include 5_3_client.qbk]
[include 5_4_server.qbk]
[include 5_5_messages.qbk]
[include 5_6_control.qbk]
[include 5_7_notes.qbk]
[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]
[endsect]

View File

@ -46,7 +46,7 @@ and `ssl::context` arguments are forwarded to the wrapped stream's constructor:
[heading Non-owning References]
If a socket type supports move construction, a websocket stream may be
constructed around the already existing socket by invoke the move
constructed around the already existing socket by invoking the move
constructor signature:
```
...

View File

@ -58,8 +58,8 @@ signature:
When a ping callback is registered, all pings and pongs received through
either synchronous read functions or asynchronous read functions will
invoke the ping callback, with the value of `is_pong` set to `true` if a
pong was received else `false` if a ping was received. The payload of
the ping or pong control frame is passed in the payload argument.
pong was received or `false` otherwise. The payload of the ping or pong
control frame is passed in the payload argument.
Unlike regular completion handlers used in calls to asynchronous initiation
functions, the ping callback only needs to be set once. The callback is not

21
doc/7_concepts.qbk Normal file
View File

@ -0,0 +1,21 @@
[/
Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
Distributed under the Boost Software License, Version 1.0. (See accompanying
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:concept Concepts]
[include concept/Body.qbk]
[include concept/BodyReader.qbk]
[include concept/BodyWriter.qbk]
[include concept/BufferSequence.qbk]
[include concept/DynamicBuffer.qbk]
[include concept/Field.qbk]
[include concept/FieldSequence.qbk]
[include concept/Streams.qbk]
[endsect]

View File

@ -7,20 +7,11 @@
[section:design Design Choices]
[block '''
<informaltable frame="all"><tgroup cols="1"><colspec colname="a"/><tbody><row><entry valign="top"><simplelist>
<member><link linkend="beast.design.http_message">HTTP Message Container</link></member>
<member><link linkend="beast.design.http_comparison">HTTP Comparison to Other Libraries</link></member>
<member><link linkend="beast.design.websocket_zaphoyd">Comparison to Zaphoyd Studios WebSocket++</link></member>
<member><link linkend="beast.design.faq">FAQ</link></member>
</simplelist></entry></row></tbody></tgroup></informaltable>
''']
The implementations are driven by business needs of cryptocurrency server
applications (e.g. [@https://ripple.com Ripple]) written in C++. These
needs were not met by existing solutions so Beast was written from scratch
as a solution. Beast's design philosophy avoids flaws exhibited by other
libraries:
The implementations were originally driven by business needs of cryptocurrency
server applications (e.g. [@https://github.com/ripple/rippled rippled]),
written in C++. These needs were not met by existing solutions so Beast
was written from scratch as a solution. Beast's design philosophy avoids
flaws exhibited by other libraries:
* Don't try to do too much.
@ -30,8 +21,8 @@ libraries:
* Role-symmetric interfaces; client and server the same (or close to it).
* Leave important decisions to the user, such as allocating memory or
managing flow control.
* Leave important decisions, such as allocating memory or
managing flow control, to the user.
Beast uses the __DynamicBuffer__ concept presented in the Networking TS
(__N4588__), and relies heavily on the __ConstBufferSequence__ and
@ -59,9 +50,9 @@ start. Other design goals:
* Allow for customizations, if the user needs it.
[include 6_1_http_message.qbk]
[include 6_2_http_comparison.qbk]
[include 6_3_websocket_zaphoyd.qbk]
[include 6_4_faq.qbk]
[include 8_1_http_message.qbk]
[include 8_2_http_comparison.qbk]
[include 8_3_websocket_zaphoyd.qbk]
[include 8_4_faq.qbk]
[endsect]

View File

@ -5,7 +5,7 @@
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:faq Boost Formal Review FAQ]
[section:faq FAQ]
To set realistic expectations and prevent a litany of duplicate review
statements, these notes address the most common questions and comments

View File

@ -86,7 +86,7 @@ boostbook boostdoc
<xsl:param>toc.section.depth=8 # How deep should recursive sections appear in the TOC?
<xsl:param>toc.max.depth=8 # How many levels should be created for each TOC?
<xsl:param>generate.section.toc.level=8 # Control depth of TOC generation in sections
<xsl:param>generate.toc="chapter nop section nop"
<xsl:param>generate.toc="chapter toc,title section nop reference nop"
<include>$(broot)/tools/boostbook/dtd
:
<location>temp

View File

@ -5,7 +5,7 @@
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:Body Body requirements]
[section:Body Body]
A [*Body] type is supplied as a template argument to the __message__ class. It
controls both the type of the data member of the resulting message object, and
@ -32,18 +32,17 @@ In this table:
[
If present, indicates that the body can hold a message body
parsing result. The type must meet the requirements of
[link beast.ref.BodyWriter [*BodyWriter]]. The implementation
constructs an object of this type to obtain buffers into which
parsed body octets are placed.
__BodyWriter__. The implementation constructs an object of
this type to obtain buffers into which parsed body octets
are placed.
]
]
[
[`X::reader`]
[]
[
If present, indicates that the body is serializable.
The type must meet the requirements of
[link beast.ref.BodyReader [*BodyReader]]. The implementation
If present, indicates that the body is serializable. The type
must meet the requirements of __BodyReader__. The implementation
constructs an object of this type to obtain buffers representing
the message body for serialization.
]

View File

@ -5,7 +5,7 @@
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:BodyReader BodyReader requirements]
[section:BodyReader BodyReader]
A [*BodyReader] provides an online algorithm to obtain a sequence of zero
or more buffers from a body during serialization. The implementation creates

View File

@ -5,7 +5,7 @@
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:BodyWriter BodyWriter requirements]
[section:BodyWriter BodyWriter]
A [*BodyWriter] provides an online algorithm to transfer a series of zero
or more buffers containing parsed body octets into a message container. The

View File

@ -5,9 +5,9 @@
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:BufferSequence BufferSequence requirements]
[section:BufferSequence BufferSequence]
A `BufferSequence` is a type meeting either of the following requirements:
A [*BufferSequence] is a type meeting either of the following requirements:
* __ConstBufferSequence__
* __MutableBufferSequence__

View File

@ -5,7 +5,7 @@
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:DynamicBuffer DynamicBuffer requirements]
[section:DynamicBuffer DynamicBuffer]
A dynamic buffer encapsulates memory storage that may be automatically resized
as required, where the memory is divided into an input sequence followed by an

View File

@ -5,7 +5,7 @@
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:Field Field requirements]
[section:Field Field]
A [*Field] represents a single HTTP header field/value pair.

View File

@ -5,10 +5,10 @@
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:FieldSequence FieldSequence requirements]
[section:FieldSequence FieldSequence]
A [*FieldSequence] is an iterable container whose value type meets
the requirements of [link beast.ref.Field [*Field]]. Objects that meet
the requirements of [link beast.concept.Field [*Field]]. Objects that meet
these requirements become serializable by the implementation.
In this table:
@ -23,7 +23,8 @@ In this table:
[`X::value_type`]
[]
[
A type that meets the requirements of [link beast.ref.Field [*Field]].
A type that meets the requirements of
[link beast.concept.Field [*Field]].
]
]
[
@ -31,7 +32,7 @@ In this table:
[]
[
An iterator type whose `reference` type meets the
requirements of [link beast.ref.Field [*Field]], and which
requirements of [link beast.concept.Field [*Field]], and which
satisfies all the requirements of [*ForwardIterator],
except that:

View File

@ -5,7 +5,7 @@
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:streams Streams requirements]
[section:streams Stream]
Stream types represent objects capable of performing synchronous or
asynchronous I/O. They are based on concepts from `boost::asio`.

View File

@ -99,11 +99,11 @@
</simplelist>
<bridgehead renderas="sect3">Concepts</bridgehead>
<simplelist type="vert" columns="1">
<member><link linkend="beast.ref.Body">Body</link></member>
<member><link linkend="beast.ref.BodyReader">BodyReader</link></member>
<member><link linkend="beast.ref.BodyWriter">BodyWriter</link></member>
<member><link linkend="beast.ref.Field">Field</link></member>
<member><link linkend="beast.ref.FieldSequence">FieldSequence</link></member>
<member><link linkend="beast.concept.Body">Body</link></member>
<member><link linkend="beast.concept.BodyReader">BodyReader</link></member>
<member><link linkend="beast.concept.BodyWriter">BodyWriter</link></member>
<member><link linkend="beast.concept.Field">Field</link></member>
<member><link linkend="beast.concept.FieldSequence">FieldSequence</link></member>
</simplelist>
</entry>
<entry valign="top">
@ -223,11 +223,11 @@
<entry valign="top">
<bridgehead renderas="sect3">Concepts</bridgehead>
<simplelist type="vert" columns="1">
<member><link linkend="beast.ref.streams.AsyncStream">AsyncStream</link></member>
<member><link linkend="beast.ref.BufferSequence">BufferSequence</link></member>
<member><link linkend="beast.ref.DynamicBuffer">DynamicBuffer</link></member>
<member><link linkend="beast.ref.streams.Stream">Stream</link></member>
<member><link linkend="beast.ref.streams.SyncStream">SyncStream</link></member>
<member><link linkend="beast.concept.streams.AsyncStream">AsyncStream</link></member>
<member><link linkend="beast.concept.BufferSequence">BufferSequence</link></member>
<member><link linkend="beast.concept.DynamicBuffer">DynamicBuffer</link></member>
<member><link linkend="beast.concept.streams.Stream">Stream</link></member>
<member><link linkend="beast.concept.streams.SyncStream">SyncStream</link></member>
</simplelist>
</entry>
<entry valign="top">

View File

@ -1609,7 +1609,7 @@
<xsl:text> </xsl:text>
<xsl:choose>
<xsl:when test="type = 'class AsyncStream'">
<xsl:text>class ``[link beast.ref.streams.AsyncStream [*AsyncStream]]``</xsl:text>
<xsl:text>class ``[link beast.concept.streams.AsyncStream [*AsyncStream]]``</xsl:text>
</xsl:when>
<xsl:when test="type = 'class AsyncReadStream'">
<xsl:text>class __AsyncReadStream__</xsl:text>
@ -1618,14 +1618,14 @@
<xsl:text>class __AsyncWriteStream__</xsl:text>
</xsl:when>
<xsl:when test="type = 'class Body'">
<xsl:text>class ``[link beast.ref.Body [*Body]]``</xsl:text>
<xsl:text>class ``[link beast.concept.Body [*Body]]``</xsl:text>
</xsl:when>
<xsl:when test="type = 'class BufferSequence'">
<xsl:text>class ``[link beast.ref.BufferSequence [*BufferSequence]]``</xsl:text>
<xsl:text>class ``[link beast.concept.BufferSequence [*BufferSequence]]``</xsl:text>
</xsl:when>
<xsl:when test="(type = 'class' or type = 'class...') and declname = 'BufferSequence'">
<xsl:value-of select="type"/>
<xsl:text> ``[link beast.ref.BufferSequence [*BufferSequence]]``</xsl:text>
<xsl:text> ``[link beast.concept.BufferSequence [*BufferSequence]]``</xsl:text>
</xsl:when>
<xsl:when test="declname = 'CompletionHandler' or type = 'class CompletionHandler'">
<xsl:text>class __CompletionHandler__</xsl:text>
@ -1634,7 +1634,7 @@
<xsl:text>class __ConstBufferSequence__</xsl:text>
</xsl:when>
<xsl:when test="declname = 'DynamicBuffer' or type = 'class DynamicBuffer'">
<xsl:text>class ``[link beast.ref.DynamicBuffer [*DynamicBuffer]]``</xsl:text>
<xsl:text>class ``[link beast.concept.DynamicBuffer [*DynamicBuffer]]``</xsl:text>
</xsl:when>
<xsl:when test="declname = 'Handler' or type = 'class Handler'">
<xsl:text>class __Handler__</xsl:text>
@ -1642,14 +1642,11 @@
<xsl:when test="declname = 'MutableBufferSequence' or type = 'class MutableBufferSequence'">
<xsl:text>class __MutableBufferSequence__</xsl:text>
</xsl:when>
<xsl:when test="declname = 'Parser' or type = 'class Parser'">
<xsl:text>class ``[link beast.ref.Parser [*Parser]]``</xsl:text>
</xsl:when>
<xsl:when test="declname = 'Stream' or type = 'class Stream'">
<xsl:text>class ``[link beast.ref.streams.Stream [*Stream]]``</xsl:text>
<xsl:text>class ``[link beast.concept.streams.Stream [*Stream]]``</xsl:text>
</xsl:when>
<xsl:when test="type = 'class SyncStream'">
<xsl:text>class ``[link beast.ref.streams.SyncStream [*SyncStream]]``</xsl:text>
<xsl:text>class ``[link beast.concept.streams.SyncStream [*SyncStream]]``</xsl:text>
</xsl:when>
<xsl:when test="declname = 'SyncReadStream' or type = 'class SyncReadStream'">
<xsl:text>class __SyncReadStream__</xsl:text>

View File

@ -0,0 +1,890 @@
//
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the 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 <beast.hpp>
/*
This file contains all of the example code snippets contained
in the documentation, which directly includes this source code.
*/
// The documentation assumes the beast::http namespace
namespace beast {
namespace http {
//------------------------------------------------------------------------------
//
// Example: Expect 100-continue
//
//------------------------------------------------------------------------------
//[http_sample_send_expect_100_continue
/** Send a request with Expect: 100-continue
This function will send a request with the Expect: 100-continue
field by first sending the header, then waiting for a successful
response from the server before continuing to send the body. If
a non-successful server response is received, the function
returns immediately.
@param stream The remote HTTP server stream.
@param buffer The buffer used for reading.
@param req The request to send. This function modifies the object:
the Expect header field is inserted into the message if it does
not already exist, and set to "100-continue".
@param ec Set to the error, if any occurred.
*/
template<
class SyncStream,
class DynamicBuffer,
class Body, class Fields>
void
send_expect_100_continue(
SyncStream& stream,
DynamicBuffer& buffer,
request<Body, Fields>& req,
error_code& ec)
{
static_assert(is_sync_stream<SyncStream>::value,
"SyncStream requirements not met");
static_assert(is_dynamic_buffer<DynamicBuffer>::value,
"DynamicBuffer requirements not met");
// Insert or replace the Expect field
req.fields.replace("Expect", "100-continue");
// Create the serializer
auto sr = make_serializer(req);
// Send just the header
write_header(stream, sr, ec);
if(ec)
return;
// Read the response from the server.
// A robust client could set a timeout here.
{
response<string_body> res;
read(stream, buffer, res, ec);
if(ec)
return;
if(res.result() != status::continue_)
{
// The server indicated that it will not
// accept the request, so skip sending the body.
return;
}
}
// Server is OK with the request, send the body
write(stream, sr, ec);
}
//]
//[http_sample_receive_expect_100_continue
/** Receive a request, handling Expect: 100-continue if present.
This function will read a request from the specified stream.
If the request contains the Expect: 100-continue field, a
status response will be delivered.
@param stream The remote HTTP client stream.
@param buffer The buffer used for reading.
@param ec Set to the error, if any occurred.
*/
template<
class SyncStream,
class DynamicBuffer>
void
receive_expect_100_continue(
SyncStream& stream,
DynamicBuffer& buffer,
error_code& ec)
{
static_assert(is_sync_stream<SyncStream>::value,
"SyncStream requirements not met");
static_assert(is_dynamic_buffer<DynamicBuffer>::value,
"DynamicBuffer requirements not met");
// Declare a parser for a request with a string body
request_parser<string_body> parser;
// Read the header
read_header(stream, buffer, parser, ec);
if(ec)
return;
// Check for the Expect field value
if(parser.get().fields["Expect"] == "100-continue")
{
// send 100 response
response<empty_body> res;
res.version = 11;
res.result(status::continue_);
res.fields.insert("Server", "test");
write(stream, res, ec);
if(ec)
return;
}
// Read the rest of the message.
//
// We use parser.base() to return a basic_parser&, to avoid an
// ambiguous function error (from boost::asio::read). Another
// solution is to qualify the call, e.g. `beast::http::read`
//
read(stream, buffer, parser.base(), ec);
}
//]
//------------------------------------------------------------------------------
//
// Example: Send Child Process Output
//
//------------------------------------------------------------------------------
//[http_sample_send_cgi_response
/** Send the output of a child process as an HTTP response.
The output of the child process comes from a @b SyncReadStream. Data
will be sent continuously as it is produced, without the requirement
that the entire process output is buffered before being sent. The
response will use the chunked transfer encoding.
@param input A stream to read the child process output from.
@param output A stream to write the HTTP response to.
@param ec Set to the error, if any occurred.
*/
template<
class SyncReadStream,
class SyncWriteStream>
void
send_cgi_response(
SyncReadStream& input,
SyncWriteStream& output,
error_code& ec)
{
static_assert(is_sync_read_stream<SyncReadStream>::value,
"SyncReadStream requirements not met");
static_assert(is_sync_write_stream<SyncWriteStream>::value,
"SyncWriteStream requirements not met");
using boost::asio::buffer_cast;
using boost::asio::buffer_size;
// Set up the response. We use the buffer_body type,
// allowing serialization to use manually provided buffers.
message<false, buffer_body, fields> res;
res.result(status::ok);
res.version = 11;
res.fields.insert("Server", "Beast");
res.fields.insert("Transfer-Encoding", "chunked");
// No data yet, but we set more = true to indicate
// that it might be coming later. Otherwise the
// serializer::is_done would return true right after
// sending the header.
res.body.data = nullptr;
res.body.more = true;
// Create the serializer. We set the split option to
// produce the header immediately without also trying
// to acquire buffers from the body (which would return
// the error http::need_buffer because we set `data`
// to `nullptr` above).
auto sr = make_serializer(res);
// Send the header immediately.
write_header(output, sr, ec);
if(ec)
return;
// Alternate between reading from the child process
// and sending all the process output until there
// is no more output.
do
{
// Read a buffer from the child process
char buffer[2048];
auto bytes_transferred = input.read_some(
boost::asio::buffer(buffer, sizeof(buffer)), ec);
if(ec == boost::asio::error::eof)
{
ec = {};
// `nullptr` indicates there is no buffer
res.body.data = nullptr;
// `false` means no more data is coming
res.body.more = false;
}
else
{
if(ec)
return;
// Point to our buffer with the bytes that
// we received, and indicate that there may
// be some more data coming
res.body.data = buffer;
res.body.size = bytes_transferred;
res.body.more = true;
}
// Write everything in the body buffer
write(output, sr, ec);
// This error is returned by body_buffer during
// serialization when it is done sending the data
// provided and needs another buffer.
if(ec == error::need_buffer)
{
ec = {};
continue;
}
if(ec)
return;
}
while(! sr.is_done());
}
//]
//--------------------------------------------------------------------------
//
// Example: HEAD Request
//
//--------------------------------------------------------------------------
//[http_sample_do_head_response
/** Handle a HEAD request for a resource.
*/
template<
class SyncStream,
class DynamicBuffer
>
void do_server_head(
SyncStream& stream,
DynamicBuffer& buffer,
error_code& ec)
{
static_assert(is_sync_stream<SyncStream>::value,
"SyncStream requirements not met");
static_assert(is_dynamic_buffer<DynamicBuffer>::value,
"DynamicBuffer requirments not met");
// We deliver this payload for all GET requests
static std::string const payload = "Hello, world!";
// Read the request
request<string_body> req;
read(stream, buffer, req, ec);
if(ec)
return;
// Set up the response, starting with the common fields
response<string_body> res;
res.version = 11;
res.fields.insert("Server", "test");
// Now handle request-specific fields
switch(req.method())
{
case verb::head:
case verb::get:
{
// A HEAD request is handled by delivering the same
// set of headers that would be sent for a GET request,
// including the Content-Length, except for the body.
res.result(status::ok);
res.fields.insert("Content-Length", payload.size());
// For GET requests, we include the body
if(req.method() == verb::get)
{
// We deliver the same payload for GET requests
// regardless of the target. A real server might
// deliver a file based on the target.
res.body = payload;
}
break;
}
default:
{
// We return responses indicating an error if
// we do not recognize the request method.
res.result(status::bad_request);
res.fields.insert("Content-Type", "text/plain");
res.body = "Invalid request-method '" + req.method_string().to_string() + "'";
break;
}
}
// Send the response
write(stream, res, ec);
if(ec)
return;
}
//]
//[http_sample_do_head_request
/** Send a HEAD request for a resource.
This function submits a HEAD request for the specified resource
and returns the response.
@param res The response. This is an output parameter.
@param stream The synchronous stream to use.
@param buffer The buffer to use.
@param target The request target.
@param ec Set to the error, if any occurred.
@throws std::invalid_argument if target is empty.
*/
template<
class SyncStream,
class DynamicBuffer
>
response<empty_body>
do_head_request(
SyncStream& stream,
DynamicBuffer& buffer,
string_view target,
error_code& ec)
{
// Do some type checking to be a good citizen
static_assert(is_sync_stream<SyncStream>::value,
"SyncStream requirements not met");
static_assert(is_dynamic_buffer<DynamicBuffer>::value,
"DynamicBuffer requirments not met");
// The interfaces we are using are low level and do not
// perform any checking of arguments; so we do it here.
if(target.empty())
throw std::invalid_argument("target may not be empty");
// Build the HEAD request for the target
request<empty_body> req;
req.version = 11;
req.method(verb::head);
req.target(target);
req.fields.insert("User-Agent", "test");
// A client MUST send a Host header field in all HTTP/1.1 request messages.
// https://tools.ietf.org/html/rfc7230#section-5.4
req.fields.insert("Host", "localhost");
// Now send it
write(stream, req, ec);
if(ec)
return {};
// Create a parser to read the response.
// Responses to HEAD requests MUST NOT include
// a body, so we use the `empty_body` type and
// only attempt to read the header.
parser<false, empty_body> p;
read_header(stream, buffer, p, ec);
if(ec)
return {};
// Transfer ownership of the response to the caller.
return p.release();
}
//]
//------------------------------------------------------------------------------
//
// Example: HTTP Relay
//
//------------------------------------------------------------------------------
//[http_sample_relay
/** Relay an HTTP message.
This function efficiently relays an HTTP message from a downstream
client to an upstream server, or from an upstream server to a
downstream client. After the message header is read from the input,
a user provided transformation function is invoked which may change
the contents of the header before forwarding to the output. This may
be used to adjust fields such as Server, or proxy fields.
@param output The stream to write to.
@param input The stream to read from.
@param buffer The buffer to use for the input.
@param transform The header transformation to apply. The function will
be called with this signature:
@code
void transform(
header<isRequest, Fields>&, // The header to transform
error_code&); // Set to the error, if any
@endcode
@param ec Set to the error if any occurred.
@tparam isRequest `true` to relay a request.
@tparam Fields The type of fields to use for the message.
*/
template<
bool isRequest,
class Fields = fields,
class SyncWriteStream,
class SyncReadStream,
class DynamicBuffer,
class Transform>
void
relay(
SyncWriteStream& output,
SyncReadStream& input,
DynamicBuffer& buffer,
error_code& ec,
Transform&& transform)
{
static_assert(is_sync_write_stream<SyncWriteStream>::value,
"SyncWriteStream requirements not met");
static_assert(is_sync_read_stream<SyncReadStream>::value,
"SyncReadStream requirements not met");
// A small buffer for relaying the body piece by piece
char buf[2048];
// Create a parser with a buffer body to read from the input.
parser<isRequest, buffer_body, Fields> p;
// Create a serializer from the message contained in the parser.
serializer<isRequest, buffer_body, Fields> sr{p.get()};
// Read just the header from the input
read_header(input, buffer, p, ec);
if(ec)
return;
// Apply the caller's header tranformation
// base() returns a reference to the header portion of the message.
transform(p.get().base(), ec);
if(ec)
return;
// Send the transformed message to the output
write_header(output, sr, ec);
if(ec)
return;
// Loop over the input and transfer it to the output
do
{
if(! p.is_done())
{
// Set up the body for writing into our small buffer
p.get().body.data = buf;
p.get().body.size = sizeof(buf);
// Read as much as we can
read(input, buffer, p, ec);
// This error is returned when buffer_body uses up the buffer
if(ec == error::need_buffer)
ec = {};
if(ec)
return;
// Set up the body for reading.
// This is how much was parsed:
p.get().body.size = sizeof(buf) - p.get().body.size;
p.get().body.data = buf;
p.get().body.more = ! p.is_done();
}
else
{
p.get().body.data = nullptr;
p.get().body.size = 0;
}
// Write everything in the buffer (which might be empty)
write(output, sr, ec);
// This error is returned when buffer_body uses up the buffer
if(ec == error::need_buffer)
ec = {};
if(ec)
return;
}
while(! p.is_done() && ! sr.is_done());
}
//]
//------------------------------------------------------------------------------
//
// Example: Serialize to std::ostream
//
//------------------------------------------------------------------------------
//[http_sample_write_ostream
// The detail namespace means "not public"
namespace detail {
// This helper is needed for C++11.
// When invoked with a buffer sequence, writes the buffers `to the std::ostream`.
template<class Serializer>
class write_ostream_helper
{
Serializer& sr_;
std::ostream& os_;
public:
write_ostream_helper(Serializer& sr, std::ostream& os)
: sr_(sr)
, os_(os)
{
}
// This function is called by the serializer
template<class ConstBufferSequence>
void
operator()(error_code& ec, ConstBufferSequence const& buffers) const
{
// These asio functions are needed to access a buffer's contents
using boost::asio::buffer_cast;
using boost::asio::buffer_size;
// Keep a running total of how much we wrote
std::size_t bytes_transferred = 0;
// Loop over the buffer sequence
for(auto it = buffers.begin(); it != buffers.end(); ++ it)
{
// This is the next buffer in the sequence
boost::asio::const_buffer const buffer = *it;
// Write it to the std::ostream
os_.write(
buffer_cast<char const*>(buffer),
buffer_size(buffer));
// If the std::ostream fails, convert it to an error code
if(os_.fail())
{
ec = make_error_code(errc::io_error);
return;
}
// Adjust our running total
bytes_transferred += buffer_size(buffer);
}
// Inform the serializer of the amount we consumed
sr_.consume(bytes_transferred);
}
};
} // detail
/** Write a message to a `std::ostream`.
This function writes the serialized representation of the
HTTP/1 message to the sream.
@param os The `std::ostream` to write to.
@param msg The message to serialize.
@param ec Set to the error, if any occurred.
*/
template<
bool isRequest,
class Body,
class Fields>
void
write_ostream(
std::ostream& os,
message<isRequest, Body, Fields>& msg,
error_code& ec)
{
// Create the serializer instance
serializer<isRequest, Body, Fields> sr{msg};
// This lambda is used as the "visit" function
detail::write_ostream_helper<decltype(sr)> lambda{sr, os};
do
{
// In C++14 we could use a generic lambda but since we want
// to require only C++11, the lambda is written out by hand.
// This function call retrieves the next serialized buffers.
sr.get(ec, lambda);
if(ec)
return;
}
while(! sr.is_done());
}
//]
//------------------------------------------------------------------------------
//
// Example: Parse from std::istream
//
//------------------------------------------------------------------------------
//[http_sample_read_istream
/** Read a message from a `std::istream`.
This function attempts to parse a complete HTTP/1 message from the stream.
@param is The `std::istream` to read from.
@param buffer The buffer to use.
@param msg The message to store the result.
@param ec Set to the error, if any occurred.
*/
template<
class Allocator,
bool isRequest,
class Body,
class Fields>
void
read_istream(
std::istream& is,
basic_flat_buffer<Allocator>& buffer,
message<isRequest, Body, Fields>& msg,
error_code& ec)
{
// Create the message parser
parser<isRequest, Body, Fields> parser;
do
{
// Extract whatever characters are presently available in the istream
if(is.rdbuf()->in_avail() > 0)
{
// Get a mutable buffer sequence for writing
auto const mb = buffer.prepare(is.rdbuf()->in_avail());
// Now get everything we can from the istream
buffer.commit(is.readsome(
boost::asio::buffer_cast<char*>(mb),
boost::asio::buffer_size(mb)));
}
else if(buffer.size() == 0)
{
// Our buffer is empty and we need more characters,
// see if we've reached the end of file on the istream
if(! is.eof())
{
// Get a mutable buffer sequence for writing
auto const mb = buffer.prepare(1024);
// Try to get more from the istream. This might block.
is.read(
boost::asio::buffer_cast<char*>(mb),
boost::asio::buffer_size(mb));
// If an error occurs on the istream then return it to the caller.
if(is.fail() && ! is.eof())
{
// We'll just re-use io_error since std::istream has no error_code interface.
ec = make_error_code(errc::io_error);
return;
}
// Commit the characters we got to the buffer.
buffer.commit(is.gcount());
}
else
{
// Inform the parser that we've reached the end of the istream.
parser.put_eof(ec);
if(ec)
return;
break;
}
}
// Write the data to the parser
auto const bytes_used = parser.put(buffer.data(), ec);
// This error means that the parser needs additional octets.
if(ec == error::need_more)
ec = {};
if(ec)
return;
// Consume the buffer octets that were actually parsed.
buffer.consume(bytes_used);
}
while(! parser.is_done());
// Transfer ownership of the message container in the parser to the caller.
msg = parser.release();
}
//]
//------------------------------------------------------------------------------
//
// Example: Custom Parser
//
//------------------------------------------------------------------------------
//[http_sample_custom_parser
template<bool isRequest>
class custom_parser
: public basic_parser<isRequest, custom_parser<isRequest>>
{
friend class basic_parser<isRequest, custom_parser>;
/// Called after receiving the request-line (isRequest == true).
void
on_request(
string_view method, // The method
string_view target, // The request-target
int version, // The HTTP-version
error_code& ec); // The error returned to the caller, if any
/// Called after receiving the start-line (isRequest == false).
void
on_response(
int code, // The status-code
string_view reason, // The obsolete reason-phrase
int version, // The HTTP-version
error_code& ec); // The error returned to the caller, if any
/// Called after receiving a header field.
void
on_field(
string_view name, // The field name
string_view value, // The field value
error_code& ec); // The error returned to the caller, if any
/// Called after the complete header is received.
void
on_header(
error_code& ec); // The error returned to the caller, if any
/// Called just before processing the body, if a body exists.
void
on_body(boost::optional<
std::uint64_t> const&
content_length, // Content length if known, else `boost::none`
error_code& ec); // The error returned to the caller, if any
/// Called for each piece of the body, if a body exists.
//
// If present, the chunked Transfer-Encoding will be removed
// before this callback is invoked.
//
void
on_data(
string_view s, // A portion of the body
error_code& ec); // The error returned to the caller, if any
/// Called for each chunk header.
void
on_chunk(
std::uint64_t size, // The size of the upcoming chunk
string_view extension, // The chunk-extension (may be empty)
error_code& ec); // The error returned to the caller, if any
/// Called when the complete message is parsed.
void
on_complete(error_code& ec);
public:
custom_parser() = default;
};
//]
// Definitions are not part of the docs but necessary to link
template<bool isRequest>
void custom_parser<isRequest>::
on_request(string_view method, string_view path, int version, error_code& ec)
{
}
template<bool isRequest>
void custom_parser<isRequest>::
on_response(int status, string_view reason, int version, error_code& ec)
{
}
template<bool isRequest>
void custom_parser<isRequest>::
on_field(string_view name, string_view value, error_code& ec)
{
}
template<bool isRequest>
void custom_parser<isRequest>::
on_header(error_code& ec)
{
}
template<bool isRequest>
void custom_parser<isRequest>::
on_body(boost::optional<std::uint64_t> const& content_length, error_code& ec)
{
}
template<bool isRequest>
void custom_parser<isRequest>::
on_data(string_view s, error_code& ec)
{
}
template<bool isRequest>
void custom_parser<isRequest>::
on_chunk(std::uint64_t size, string_view extension, error_code& ec)
{
}
template<bool isRequest>
void custom_parser<isRequest>::
on_complete(error_code& ec)
{
}
} // http
} // beast

View File

@ -11,6 +11,18 @@
#include <memory>
#include <utility>
//[core_sample_echo_op_1
// Read a line and echo it back
//
template<class AsyncStream, class CompletionToken>
beast::async_return_type<CompletionToken, void(beast::error_code)>
async_echo(AsyncStream& stream, CompletionToken&& token);
//]
//[core_sample_echo_op_3
// This composed operation reads a line of input and echoes it back.
//
template<class AsyncStream, class Handler>
@ -127,9 +139,10 @@ public:
void operator()(beast::error_code ec, std::size_t bytes_transferred);
};
// We are callable with the signature void(error_code, bytes_transferred),
// allowing `*this` to be used as both a ReadHandler and a WriteHandler.
//
//]
//[core_sample_echo_op_4
template<class AsyncStream, class Handler>
void echo_op<AsyncStream, Handler>::
operator()(beast::error_code ec, std::size_t bytes_transferred)
@ -170,6 +183,13 @@ operator()(beast::error_code ec, std::size_t bytes_transferred)
return;
}
//]
//[core_sample_echo_op_2
template<class AsyncStream, class Handler>
class echo_op;
// Read a line and echo it back
//
template<class AsyncStream, class CompletionToken>
@ -205,6 +225,8 @@ async_echo(AsyncStream& stream, CompletionToken&& token)
return init.result.get();
}
//]
int main()
{
using address_type = boost::asio::ip::address;

View File

@ -5,6 +5,8 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[http_example_get
#include <beast/core.hpp>
#include <beast/http.hpp>
#include <boost/asio.hpp>
@ -39,3 +41,5 @@ int main()
beast::http::read(sock, b, res);
std::cout << res << std::endl;
}
//]

View File

@ -5,6 +5,8 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[websocket_example_client_echo
#include <beast/core.hpp>
#include <beast/websocket.hpp>
#include <boost/asio.hpp>
@ -33,3 +35,5 @@ int main()
ws.close(beast::websocket::close_code::normal);
std::cout << beast::buffers(b.data()) << "\n";
}
//]

19
include/beast.hpp Normal file
View File

@ -0,0 +1,19 @@
//
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BEAST_HPP
#define BEAST_HPP
#include <beast/config.hpp>
#include <beast/core.hpp>
#include <beast/http.hpp>
#include <beast/version.hpp>
#include <beast/websocket.hpp>
#include <beast/zlib.hpp>
#endif

View File

@ -121,7 +121,7 @@ struct header<true, Fields>
@ref verb::unknown is returned. Callers may use @ref method_string
to retrieve the exact text.
@note This function is only available if `isRequest == true`.
@note This function is only available when `isRequest == true`.
@see @ref method_string
*/
@ -148,7 +148,7 @@ struct header<true, Fields>
/** Return the request-method string.
@note This function is only available if `isRequest == true`.
@note This function is only available when `isRequest == true`.
@see @ref method
*/
@ -166,7 +166,7 @@ struct header<true, Fields>
@param s A string representing the request-method.
@note This function is only available if `isRequest == true`.
@note This function is only available when `isRequest == true`.
*/
void
method(string_view s)
@ -176,7 +176,7 @@ struct header<true, Fields>
/** Returns the request-target string.
@note This function is only available if `isRequest == true`.
@note This function is only available when `isRequest == true`.
*/
string_view
target() const
@ -188,7 +188,7 @@ struct header<true, Fields>
@param s A string representing the request-target.
@note This function is only available if `isRequest == true`.
@note This function is only available when `isRequest == true`.
*/
void
target(string_view s)
@ -295,7 +295,7 @@ struct header<false, Fields>
function returns @ref status::unknown. Use @ref result_int
to return the raw status code as a number.
@note This member is only available if `isRequest == false`.
@note This member is only available when `isRequest == false`.
*/
status
result() const
@ -308,7 +308,7 @@ struct header<false, Fields>
@param v The code to set.
@note This member is only available if `isRequest == false`.
@note This member is only available when `isRequest == false`.
*/
void
result(status v)
@ -337,7 +337,7 @@ struct header<false, Fields>
This returns the raw status code as an integer, even
when that code is not in the list of known status codes.
@note This member is only available if `isRequest == false`.
@note This member is only available when `isRequest == false`.
*/
int
result_int() const
@ -346,11 +346,11 @@ struct header<false, Fields>
}
/** Return the Reason-Phrase.
/** Return the response reason-phrase.
The Reason-Phrase is obsolete as of rfc7230.
The reason-phrase is obsolete as of rfc7230.
@note This function is only available if `isRequest == false`.
@note This function is only available when `isRequest == false`.
*/
string_view
reason() const
@ -358,7 +358,7 @@ struct header<false, Fields>
return get_reason();
}
/** Set the Reason-Phrase
/** Set the response reason-phrase (deprecated)
This function sets a custom reason-phrase to a copy of
the string passed in. Normally it is not necessary to set
@ -370,11 +370,11 @@ struct header<false, Fields>
string. This will restore the default standard reason text
based on the status code used when serializing.
The Reason-Phrase is obsolete as of rfc7230.
The reason-phrase is obsolete as of rfc7230.
@param value A value that represents the reason phrase.
@param s The string to use for the reason-phrase.
@note This function is only available if `isRequest == false`.
@note This function is only available when `isRequest == false`.
*/
void
reason(string_view s)

View File

@ -43,7 +43,7 @@ unit-test http-tests :
../extras/beast/unit_test/main.cpp
http/basic_parser.cpp
http/buffer_body.cpp
http/design.cpp
http/doc_http_samples.cpp
http/dynamic_body.cpp
http/error.cpp
http/fields.cpp

View File

@ -1,18 +1,20 @@
# Part of Beast
GroupSources(examples examples)
GroupSources(extras/beast extras)
GroupSources(include/beast beast)
GroupSources(test/http "/")
add_executable (http-tests
${BEAST_INCLUDES}
${EXAMPLES_INCLUDES}
${EXTRAS_INCLUDES}
message_fuzz.hpp
test_parser.hpp
../../extras/beast/unit_test/main.cpp
basic_parser.cpp
buffer_body.cpp
design.cpp
doc_http_samples.cpp
dynamic_body.cpp
empty_body.cpp
error.cpp

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,404 @@
//
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the 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_http_samples.hpp>
#include <beast/core/detail/clamp.hpp>
#include <beast/core/detail/read_size_helper.hpp>
#include <beast/test/pipe_stream.hpp>
#include <beast/test/string_istream.hpp>
#include <beast/test/string_ostream.hpp>
#include <beast/test/yield_to.hpp>
#include <beast/unit_test/suite.hpp>
#include <sstream>
namespace beast {
namespace http {
class doc_http_samples_test
: public beast::unit_test::suite
, public beast::test::enable_yield_to
{
public:
// two threads, for some examples using a pipe
doc_http_samples_test()
: enable_yield_to(2)
{
}
template<bool isRequest>
bool
equal_body(string_view sv, string_view body)
{
test::string_istream si{
get_io_service(), sv.to_string()};
message<isRequest, string_body, fields> m;
multi_buffer b;
try
{
read(si, b, m);
return m.body == body;
}
catch(std::exception const& e)
{
log << "equal_body: " << e.what() << std::endl;
return false;
}
}
void
doExpect100Continue()
{
test::pipe p{ios_};
yield_to(
[&](yield_context)
{
error_code ec;
flat_buffer buffer;
receive_expect_100_continue(
p.server, buffer, ec);
BEAST_EXPECTS(! ec, ec.message());
},
[&](yield_context)
{
flat_buffer buffer;
request<string_body, fields> req;
req.version = 11;
req.method("POST");
req.target("/");
req.fields.insert("User-Agent", "test");
req.body = "Hello, world!";
prepare(req);
error_code ec;
send_expect_100_continue(
p.client, buffer, req, ec);
BEAST_EXPECTS(! ec, ec.message());
});
}
void
doCgiResponse()
{
std::string const s = "Hello, world!";
test::pipe child{ios_};
child.server.read_size(3);
ostream(child.server.buffer) << s;
child.client.close();
test::pipe p{ios_};
error_code ec;
send_cgi_response(child.server, p.client, ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(equal_body<false>(p.server.str(), s));
}
void
doRelay()
{
request<string_body, fields> req;
req.version = 11;
req.method("POST");
req.target("/");
req.fields.insert("User-Agent", "test");
req.body = "Hello, world!";
prepare(req);
test::pipe downstream{ios_};
downstream.server.read_size(3);
test::pipe upstream{ios_};
upstream.client.write_size(3);
error_code ec;
write(downstream.client, req);
BEAST_EXPECTS(! ec, ec.message());
downstream.client.close();
flat_buffer buffer;
relay<true>(upstream.client, downstream.server, buffer, ec,
[&](header<true, fields>& h, error_code& ec)
{
h.fields.erase("Content-Length");
h.fields.replace("Transfer-Encoding", "chunked");
});
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(equal_body<true>(
upstream.server.str(), req.body));
}
void
doReadStdStream()
{
std::string const s =
"HTTP/1.0 200 OK\r\n"
"User-Agent: test\r\n"
"\r\n"
"Hello, world!";
std::istringstream is(s);
error_code ec;
flat_buffer buffer;
response<string_body> res;
read_istream(is, buffer, res, ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(boost::lexical_cast<
std::string>(res) == s);
}
void
doWriteStdStream()
{
std::ostringstream os;
request<string_body> req;
req.version = 11;
req.method(verb::get);
req.target("/");
req.fields.insert("User-Agent", "test");
error_code ec;
write_ostream(os, req, ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(boost::lexical_cast<
std::string>(req) == os.str());
}
void
doCustomParser()
{
{
string_view s{
"POST / HTTP/1.1\r\n"
"User-Agent: test\r\n"
"Content-Length: 13\r\n"
"\r\n"
"Hello, world!"
};
error_code ec;
custom_parser<true> p;
p.put(boost::asio::buffer(
s.data(), s.size()), ec);
BEAST_EXPECTS(! ec, ec.message());
}
{
string_view s{
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"d\r\n"
"Hello, world!"
"\r\n"
"0\r\n\r\n"
};
error_code ec;
custom_parser<false> p;
p.put(boost::asio::buffer(
s.data(), s.size()), ec);
BEAST_EXPECTS(! ec, ec.message());
}
}
void
doHEAD()
{
test::pipe p{ios_};
yield_to(
[&](yield_context)
{
error_code ec;
flat_buffer buffer;
do_server_head(p.server, buffer, ec);
BEAST_EXPECTS(! ec, ec.message());
},
[&](yield_context)
{
error_code ec;
flat_buffer buffer;
auto res = do_head_request(p.client, buffer, "/", ec);
BEAST_EXPECTS(! ec, ec.message());
});
}
//--------------------------------------------------------------------------
//
// Deferred Body type commitment
//
//--------------------------------------------------------------------------
void
doDeferredBody()
{
test::pipe p{ios_};
ostream(p.server.buffer) <<
"POST / HTTP/1.1\r\n"
"User-Agent: test\r\n"
"Content-Length: 13\r\n"
"\r\n"
"Hello, world!";
flat_buffer buffer;
header_parser<true, fields> parser;
auto bytes_used =
read_some(p.server, buffer, parser);
buffer.consume(bytes_used);
request_parser<string_body> parser2(
std::move(parser));
while(! parser2.is_done())
{
bytes_used = read_some(p.server, buffer, parser2);
buffer.consume(bytes_used);
}
}
//--------------------------------------------------------------------------
#if 0
// VFALCO This is broken
/*
Efficiently relay a message from one stream to another
*/
template<
bool isRequest,
class SyncWriteStream,
class DynamicBuffer,
class SyncReadStream>
void
relay(
SyncWriteStream& out,
DynamicBuffer& b,
SyncReadStream& in)
{
flat_buffer buffer{4096}; // 4K limit
header_parser<isRequest, fields> parser;
serializer<isRequest, buffer_body<
typename flat_buffer::const_buffers_type>,
fields> ws{parser.get()};
error_code ec;
do
{
auto const state0 = parser.state();
auto const bytes_used =
read_some(in, buffer, parser, ec);
BEAST_EXPECTS(! ec, ec.message());
switch(state0)
{
case parse_state::header:
{
BEAST_EXPECT(parser.is_header_done());
for(;;)
{
ws.write_some(out, ec);
if(ec == http::error::need_more)
{
ec = {};
break;
}
if(ec)
BOOST_THROW_EXCEPTION(system_error{ec});
}
break;
}
case parse_state::chunk_header:
{
// inspect parser.chunk_extension() here
if(parser.is_done())
boost::asio::write(out,
chunk_encode_final());
break;
}
case parse_state::body:
case parse_state::body_to_eof:
case parse_state::chunk_body:
{
if(! parser.is_done())
{
auto const body = parser.body();
boost::asio::write(out, chunk_encode(
false, boost::asio::buffer(
body.data(), body.size())));
}
break;
}
case parse_state::complete:
break;
}
buffer.consume(bytes_used);
}
while(! parser.is_done());
}
void
testRelay()
{
// Content-Length
{
test::string_istream is{ios_,
"GET / HTTP/1.1\r\n"
"Content-Length: 5\r\n"
"\r\n" // 37 byte header
"*****",
3 // max_read
};
test::string_ostream os{ios_};
flat_buffer b{16};
relay<true>(os, b, is);
}
// end of file
{
test::string_istream is{ios_,
"HTTP/1.1 200 OK\r\n"
"\r\n" // 19 byte header
"*****",
3 // max_read
};
test::string_ostream os{ios_};
flat_buffer b{16};
relay<false>(os, b, is);
}
// chunked
{
test::string_istream is{ios_,
"GET / HTTP/1.1\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"5;x;y=1;z=\"-\"\r\n*****\r\n"
"3\r\n---\r\n"
"1\r\n+\r\n"
"0\r\n\r\n",
2 // max_read
};
test::string_ostream os{ios_};
flat_buffer b{16};
relay<true>(os, b, is);
}
}
#endif
//--------------------------------------------------------------------------
void
run()
{
doExpect100Continue();
doCgiResponse();
doRelay();
doReadStdStream();
doWriteStdStream();
doCustomParser();
doHEAD();
doDeferredBody();
}
};
BEAST_DEFINE_TESTSUITE(doc_http_samples,http,beast);
} // http
} // beast