mirror of
https://github.com/boostorg/beast.git
synced 2025-07-30 04:47:29 +02:00
Documentation work
This commit is contained in:
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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
66
doc/3_5_op_tutorial.qbk
Normal 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]
|
@ -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]
|
@ -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]
|
@ -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!".
|
||||
]]
|
||||
]
|
106
doc/4_04_serializer_streams.qbk
Normal file
106
doc/4_04_serializer_streams.qbk
Normal 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
147
doc/4_05_parser_streams.qbk
Normal 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]
|
63
doc/4_07_parser_buffers.qbk
Normal file
63
doc/4_07_parser_buffers.qbk
Normal 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]
|
@ -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]
|
@ -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]
|
@ -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
157
doc/5_http_examples.qbk
Normal 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]
|
@ -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]
|
@ -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:
|
||||
```
|
||||
...
|
@ -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
21
doc/7_concepts.qbk
Normal 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]
|
@ -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]
|
@ -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
|
@ -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
|
||||
|
@ -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.
|
||||
]
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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__
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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`.
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
|
890
examples/doc_http_samples.hpp
Normal file
890
examples/doc_http_samples.hpp
Normal 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
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
//]
|
||||
|
@ -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
19
include/beast.hpp
Normal 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
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
1131
test/http/design.cpp
1131
test/http/design.cpp
File diff suppressed because it is too large
Load Diff
404
test/http/doc_http_samples.cpp
Normal file
404
test/http/doc_http_samples.cpp
Normal 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
|
Reference in New Issue
Block a user