mirror of
https://github.com/boostorg/beast.git
synced 2025-07-31 21:34:46 +02:00
Documentation work
This commit is contained in:
@@ -141,6 +141,7 @@ elseif ("${VARIANT}" STREQUAL "release")
|
|||||||
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
include_directories (.)
|
||||||
include_directories (extras)
|
include_directories (extras)
|
||||||
include_directories (include)
|
include_directories (include)
|
||||||
|
|
||||||
@@ -172,6 +173,10 @@ file(GLOB_RECURSE BEAST_INCLUDES
|
|||||||
${PROJECT_SOURCE_DIR}/include/beast/*.ipp
|
${PROJECT_SOURCE_DIR}/include/beast/*.ipp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
file(GLOB_RECURSE EXAMPLES_INCLUDES
|
||||||
|
${PROJECT_SOURCE_DIR}/examples/*.hpp
|
||||||
|
)
|
||||||
|
|
||||||
file(GLOB_RECURSE EXTRAS_INCLUDES
|
file(GLOB_RECURSE EXTRAS_INCLUDES
|
||||||
${PROJECT_SOURCE_DIR}/extras/beast/*.hpp
|
${PROJECT_SOURCE_DIR}/extras/beast/*.hpp
|
||||||
${PROJECT_SOURCE_DIR}/extras/beast/*.ipp
|
${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 __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 __SyncWriteStream__ [@http://www.boost.org/doc/html/boost_asio/reference/SyncWriteStream.html [*SyncWriteStream]]]
|
||||||
|
|
||||||
[def __AsyncStream__ [link beast.ref.streams.AsyncStream [*AsyncStream]]]
|
[def __AsyncStream__ [link beast.concept.streams.AsyncStream [*AsyncStream]]]
|
||||||
[def __Body__ [link beast.ref.Body [*Body]]]
|
[def __Body__ [link beast.concept.Body [*Body]]]
|
||||||
[def __BodyReader__ [link beast.ref.BodyReader [*BodyReader]]]
|
[def __BodyReader__ [link beast.concept.BodyReader [*BodyReader]]]
|
||||||
[def __BodyWriter__ [link beast.ref.BodyWriter [*BodyWriter]]]
|
[def __BodyWriter__ [link beast.concept.BodyWriter [*BodyWriter]]]
|
||||||
[def __DynamicBuffer__ [link beast.ref.DynamicBuffer [*DynamicBuffer]]]
|
[def __DynamicBuffer__ [link beast.concept.DynamicBuffer [*DynamicBuffer]]]
|
||||||
[def __FieldSequence__ [link beast.ref.FieldSequence [*FieldSequence]]]
|
[def __FieldSequence__ [link beast.concept.FieldSequence [*FieldSequence]]]
|
||||||
[def __Stream__ [link beast.ref.streams [*Stream]]]
|
[def __Stream__ [link beast.concept.streams [*Stream]]]
|
||||||
[def __SyncStream__ [link beast.ref.streams.SyncStream [*SyncStream]]]
|
[def __SyncStream__ [link beast.concept.streams.SyncStream [*SyncStream]]]
|
||||||
|
|
||||||
[def __basic_fields__ [link beast.ref.http__basic_fields `basic_fields`]]
|
[def __basic_fields__ [link beast.ref.http__basic_fields `basic_fields`]]
|
||||||
[def __basic_multi_buffer__ [link beast.ref.basic_multi_buffer `basic_multi_buffer`]]
|
[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 __parser__ [link beast.ref.http__parser `parser`]]
|
||||||
[def __serializer__ [link beast.ref.http__serializer `serializer`]]
|
[def __serializer__ [link beast.ref.http__serializer `serializer`]]
|
||||||
|
|
||||||
Beast is a cross-platform, header-only C++11 library for low-level HTTP
|
[import ../examples/http_example.cpp]
|
||||||
and WebSocket protocol programming that uses the consistent network and
|
[import ../examples/websocket_example.cpp]
|
||||||
asynchronous model of __Asio__.
|
[import ../examples/echo_op.cpp]
|
||||||
|
[import ../examples/doc_http_samples.hpp]
|
||||||
[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.
|
|
||||||
]]
|
|
||||||
]
|
|
||||||
|
|
||||||
[include 1_overview.qbk]
|
[include 1_overview.qbk]
|
||||||
[include 2_examples.qbk]
|
[include 2_examples.qbk]
|
||||||
[include 3_0_core.qbk]
|
[include 3_0_core.qbk]
|
||||||
[include 4_0_http.qbk]
|
[include 4_00_http.qbk]
|
||||||
[include 5_0_websocket.qbk]
|
[include 5_http_examples.qbk]
|
||||||
[include 6_0_design.qbk]
|
[include 6_0_websocket.qbk]
|
||||||
|
[include 7_concepts.qbk]
|
||||||
|
[include 8_0_design.qbk]
|
||||||
|
|
||||||
[section:ref Reference]
|
[section:quickref Reference]
|
||||||
[xinclude quickref.xml]
|
[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]
|
[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]
|
[xinclude index.xml]
|
||||||
|
@@ -7,10 +7,19 @@
|
|||||||
|
|
||||||
[section:overview Introduction]
|
[section:overview Introduction]
|
||||||
|
|
||||||
|
[important
|
||||||
Beast is a cross-platform, header-only C++11 library for low-level HTTP
|
Beast is a cross-platform, header-only C++11 library for low-level HTTP
|
||||||
and WebSocket protocol programming that uses the consistent networking
|
and WebSocket protocol programming that uses the consistent network and
|
||||||
and asynchronous model of __Asio__. The design of the library achieves
|
asynchronous model of __Asio__.
|
||||||
these goals:
|
|
||||||
|
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
|
* [*Symmetry.] Interfaces are role-agnostic; the same interfaces can be
|
||||||
used to build clients, servers, or both.
|
used to build clients, servers, or both.
|
||||||
@@ -41,7 +50,7 @@ Beast requires:
|
|||||||
* [*Boost.] Beast is built on Boost, especially Boost.Asio.
|
* [*Boost.] Beast is built on Boost, especially Boost.Asio.
|
||||||
* [*OpenSSL.] If using TLS/Secure sockets (optional).
|
* [*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,
|
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
|
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]
|
[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
|
especially for the HTTP and WebSocket protocols. The author theorizes
|
||||||
that previous attempts to build such libraries focused too much on end
|
that previous attempts to build such libraries focused too much on end
|
||||||
user needs instead of applying principles of computer science to solve
|
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
|
a few) are not directly provided, but nothing stops users from creating
|
||||||
these features using Beast's HTTP message types.
|
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]
|
[heading Credits]
|
||||||
|
|
||||||
Boost.Asio is the inspiration behind which all of the interfaces and
|
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
|
documentation. Credit goes to Christopher Kohlhoff for the wonderful
|
||||||
Asio library and the ideas upon which Beast is built.
|
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
|
contributed by
|
||||||
David Schwartz,
|
[@https://github.com/JoelKatz David Schwartz],
|
||||||
Edward Hennis,
|
[@https://github.com/ximinez Edward Hennis],
|
||||||
[@https://github.com/howardhinnant Howard Hinnant],
|
[@https://github.com/howardhinnant Howard Hinnant],
|
||||||
Miguel Portilla,
|
[@https://github.com/miguelportilla Miguel Portilla],
|
||||||
Nikolaos Bougalis,
|
[@https://github.com/nbougalis Nik Bougalis],
|
||||||
Scott Determan,
|
[@https://github.com/seelabs Scott Determan],
|
||||||
Scott Schurr,
|
[@https://github.com/scottschurr],
|
||||||
and
|
Many thanks to
|
||||||
[@https://www.ripple.com Ripple Labs]
|
[@https://github.com/K-ballo Agustín Bergé],
|
||||||
for supporting its early development. Also thanks to
|
|
||||||
Agustín Bergé,
|
|
||||||
[@http://www.boost.org/users/people/glen_fernandes.html Glen Fernandes],
|
[@http://www.boost.org/users/people/glen_fernandes.html Glen Fernandes],
|
||||||
and
|
and
|
||||||
Peter Dimov
|
[https://github.com/pdimov Peter Dimov]
|
||||||
for helping me considerably on Slack.
|
for tirelessly answering questions on
|
||||||
|
[@https://cpplang.slack.com/ Cpplang-Slack].
|
||||||
|
|
||||||
[endsect]
|
[endsect]
|
||||||
|
@@ -5,86 +5,30 @@
|
|||||||
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
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
|
These usage examples are intended to quickly impress upon readers the
|
||||||
flavor of the library. They are complete programs which may be built
|
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
|
and run. Source code and build scripts for these programs may be found
|
||||||
in the examples directory.
|
in the examples directory.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[heading HTTP GET]
|
[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:
|
||||||
|
|
||||||
```
|
[http_example_get]
|
||||||
#include <beast/core.hpp>
|
|
||||||
#include <beast/http.hpp>
|
|
||||||
#include <boost/asio.hpp>
|
|
||||||
#include <boost/lexical_cast.hpp>
|
|
||||||
#include <iostream>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
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]
|
[heading WebSocket]
|
||||||
|
|
||||||
Establish a WebSocket connection, send a message and receive the reply:
|
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()
|
[websocket_example_client_echo]
|
||||||
{
|
|
||||||
// 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 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]
|
[heading WebSocket Echo Server]
|
||||||
|
|
||||||
|
@@ -5,17 +5,7 @@
|
|||||||
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||||
]
|
]
|
||||||
|
|
||||||
[section:core Core Concepts]
|
[section:core Library Basics]
|
||||||
|
|
||||||
[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>
|
|
||||||
''']
|
|
||||||
|
|
||||||
A goal of the library is expose implementation primitives in order that
|
A goal of the library is expose implementation primitives in order that
|
||||||
users may build their own library-like components. These primitives include
|
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_2_streams.qbk]
|
||||||
[include 3_3_buffers.qbk]
|
[include 3_3_buffers.qbk]
|
||||||
[include 3_4_async.qbk]
|
[include 3_4_async.qbk]
|
||||||
[include 3_5_tutorial.qbk]
|
[include 3_5_op_tutorial.qbk]
|
||||||
|
|
||||||
[endsect]
|
[endsect]
|
||||||
|
@@ -5,13 +5,21 @@
|
|||||||
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
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
|
[warning
|
||||||
connections, or handle any aspect of connection management. In order to
|
Beast does not manage sockets, make outgoing connections,
|
||||||
invoke library algorithms it is necessary to first have a connected socket,
|
accept incoming connections, handle timeouts, close endpoints,
|
||||||
SSL stream, or other object which meets the required stream concepts. This
|
do name lookups, deal with TLS certificates, perform authentication,
|
||||||
example is provided as a reminder of how to work with sockets:
|
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";
|
auto host = "www.example.com";
|
||||||
boost::asio::ip::tcp::resolver r{ios};
|
boost::asio::ip::tcp::resolver r{ios};
|
||||||
@@ -59,3 +67,5 @@ special meaning:
|
|||||||
which is already connected with a remote host.
|
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)
|
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
|
buffers is transferred sequentially. Streams are either synchronous
|
||||||
or asynchronous, and may allow reading, writing, or both. Note that
|
or asynchronous, and may allow reading, writing, or both. Note that
|
||||||
a particular type may model more than one concept. For example, the
|
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()));
|
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)
|
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__
|
__Asio__ provides the __ConstBufferSequence__ and __MutableBufferSequence__
|
||||||
concepts, whose models provide ranges of buffers, as well as the __streambuf__
|
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.
|
transform these buffer sequences ranges efficiently using lazy evaluation.
|
||||||
No memory allocations are used in the transformations; instead, they
|
No memory allocations are used in the transformations; instead, they
|
||||||
create lightweight iterators over the existing, unmodified memory
|
create lightweight iterators over the existing, unmodified memory
|
||||||
buffers. The lifetime of the transformed buffers is retained by the
|
buffers. Control of buffers is retained by the caller; ownership is
|
||||||
caller; ownership is not transferred.
|
not transferred.
|
||||||
|
|
||||||
[table Buffer Algorithms
|
[table Buffer Algorithms
|
||||||
[[Name][Description]]
|
[[Name][Description]]
|
||||||
@@ -145,3 +145,5 @@ output streams.
|
|||||||
dynamic buffer.
|
dynamic buffer.
|
||||||
]]
|
]]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[endsect]
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
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
|
Asynchronous operations are started by calling a free function or member
|
||||||
function known as an ['asynchronous initiation function]. The initiation
|
function known as an ['asynchronous initiation function]. The initiation
|
||||||
@@ -98,3 +98,5 @@ initiation functions used to launch them are available:
|
|||||||
Extensible Asynchronous Model.
|
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]
|
[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
|
This library offers programmers simple and performant models of HTTP messages
|
||||||
and their associated operations including synchronous, asynchronous, and
|
and their associated operations including synchronous, asynchronous, and
|
||||||
buffer-oriented parsing and serialization of messages in the HTTP/1 wire
|
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_header `async_read_header`], and
|
||||||
[link beast.ref.http__async_read_some `async_read_some`]
|
[link beast.ref.http__async_read_some `async_read_some`]
|
||||||
read HTTP/1 message data from a
|
read HTTP/1 message data from a
|
||||||
[link beast.ref.streams stream].
|
[link beast.concept.streams stream].
|
||||||
]
|
]
|
||||||
][
|
][
|
||||||
[Stream Writing]
|
[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_header `async_write_header`], and
|
||||||
[link beast.ref.http__async_write_some `async_write_some`]
|
[link beast.ref.http__async_write_some `async_write_some`]
|
||||||
write HTTP/1 message data to a
|
write HTTP/1 message data to a
|
||||||
[link beast.ref.streams stream].
|
[link beast.concept.streams stream].
|
||||||
]
|
]
|
||||||
][
|
][
|
||||||
[Serialization]
|
[Serialization]
|
||||||
@@ -76,24 +62,24 @@ format using __Asio__. Specifically, the library provides:
|
|||||||
]
|
]
|
||||||
|
|
||||||
[note
|
[note
|
||||||
The following documentation assumes some familiarity with __Asio__ and
|
This documentation assumes some familiarity with __Asio__ and
|
||||||
the HTTP protocol specification described in __rfc7230__. Sample code
|
the HTTP protocol specification described in __rfc7230__. Sample
|
||||||
and identifiers mentioned in the HTTP documentation sections are
|
code and identifiers mentioned in this section is written as if
|
||||||
written as if these declarations are in effect:
|
these declarations are in effect:
|
||||||
```
|
```
|
||||||
#include <beast/http.hpp>
|
#include <beast/http.hpp>
|
||||||
using namespace beast::http;
|
using namespace beast::http;
|
||||||
```
|
```
|
||||||
]
|
]
|
||||||
|
|
||||||
[include 4_1_primer.qbk]
|
[include 4_01_primer.qbk]
|
||||||
[include 4_2_message.qbk]
|
[include 4_02_message.qbk]
|
||||||
[include 4_3_streams.qbk]
|
[include 4_03_streams.qbk]
|
||||||
[include 4_4_serializer_streams.qbk]
|
[include 4_04_serializer_streams.qbk]
|
||||||
[include 4_5_parser_streams.qbk]
|
[include 4_05_parser_streams.qbk]
|
||||||
[include 4_6_serializer_buffers.qbk]
|
[include 4_06_serializer_buffers.qbk]
|
||||||
[include 4_7_parser_buffers.qbk]
|
[include 4_07_parser_buffers.qbk]
|
||||||
[include 4_8_custom_parsers.qbk]
|
[include 4_08_custom_parsers.qbk]
|
||||||
[include 4_9_custom_body.qbk]
|
[include 4_09_custom_body.qbk]
|
||||||
|
|
||||||
[endsect]
|
[endsect]
|
@@ -5,7 +5,7 @@
|
|||||||
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
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
|
The HTTP protocol defines the
|
||||||
[@https://tools.ietf.org/html/rfc7230#section-2.1 client and server roles]:
|
[@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
|
This response has a
|
||||||
[@https://tools.ietf.org/html/rfc7231#section-6 200 status code]
|
[@https://tools.ietf.org/html/rfc7231#section-6 200 status code]
|
||||||
meaning the operation requested completed successfully. The obsolete
|
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!".
|
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]
|
[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
|
The WebSocket Protocol enables two-way communication between a client
|
||||||
running untrusted code in a controlled environment to a remote host that has
|
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
|
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 6_1_streams.qbk]
|
||||||
[include 5_2_connect.qbk]
|
[include 6_2_connect.qbk]
|
||||||
[include 5_3_client.qbk]
|
[include 6_3_client.qbk]
|
||||||
[include 5_4_server.qbk]
|
[include 6_4_server.qbk]
|
||||||
[include 5_5_messages.qbk]
|
[include 6_5_messages.qbk]
|
||||||
[include 5_6_control.qbk]
|
[include 6_6_control.qbk]
|
||||||
[include 5_7_notes.qbk]
|
[include 6_7_notes.qbk]
|
||||||
|
|
||||||
[endsect]
|
[endsect]
|
@@ -46,7 +46,7 @@ and `ssl::context` arguments are forwarded to the wrapped stream's constructor:
|
|||||||
[heading Non-owning References]
|
[heading Non-owning References]
|
||||||
|
|
||||||
If a socket type supports move construction, a websocket stream may be
|
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:
|
constructor signature:
|
||||||
```
|
```
|
||||||
...
|
...
|
@@ -58,8 +58,8 @@ signature:
|
|||||||
When a ping callback is registered, all pings and pongs received through
|
When a ping callback is registered, all pings and pongs received through
|
||||||
either synchronous read functions or asynchronous read functions will
|
either synchronous read functions or asynchronous read functions will
|
||||||
invoke the ping callback, with the value of `is_pong` set to `true` if a
|
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
|
pong was received or `false` otherwise. The payload of the ping or pong
|
||||||
the ping or pong control frame is passed in the payload argument.
|
control frame is passed in the payload argument.
|
||||||
|
|
||||||
Unlike regular completion handlers used in calls to asynchronous initiation
|
Unlike regular completion handlers used in calls to asynchronous initiation
|
||||||
functions, the ping callback only needs to be set once. The callback is not
|
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]
|
[section:design Design Choices]
|
||||||
|
|
||||||
[block '''
|
The implementations were originally driven by business needs of cryptocurrency
|
||||||
<informaltable frame="all"><tgroup cols="1"><colspec colname="a"/><tbody><row><entry valign="top"><simplelist>
|
server applications (e.g. [@https://github.com/ripple/rippled rippled]),
|
||||||
<member><link linkend="beast.design.http_message">HTTP Message Container</link></member>
|
written in C++. These needs were not met by existing solutions so Beast
|
||||||
<member><link linkend="beast.design.http_comparison">HTTP Comparison to Other Libraries</link></member>
|
was written from scratch as a solution. Beast's design philosophy avoids
|
||||||
<member><link linkend="beast.design.websocket_zaphoyd">Comparison to Zaphoyd Studios WebSocket++</link></member>
|
flaws exhibited by other libraries:
|
||||||
<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:
|
|
||||||
|
|
||||||
* Don't try to do too much.
|
* Don't try to do too much.
|
||||||
|
|
||||||
@@ -30,8 +21,8 @@ libraries:
|
|||||||
|
|
||||||
* Role-symmetric interfaces; client and server the same (or close to it).
|
* Role-symmetric interfaces; client and server the same (or close to it).
|
||||||
|
|
||||||
* Leave important decisions to the user, such as allocating memory or
|
* Leave important decisions, such as allocating memory or
|
||||||
managing flow control.
|
managing flow control, to the user.
|
||||||
|
|
||||||
Beast uses the __DynamicBuffer__ concept presented in the Networking TS
|
Beast uses the __DynamicBuffer__ concept presented in the Networking TS
|
||||||
(__N4588__), and relies heavily on the __ConstBufferSequence__ and
|
(__N4588__), and relies heavily on the __ConstBufferSequence__ and
|
||||||
@@ -59,9 +50,9 @@ start. Other design goals:
|
|||||||
|
|
||||||
* Allow for customizations, if the user needs it.
|
* Allow for customizations, if the user needs it.
|
||||||
|
|
||||||
[include 6_1_http_message.qbk]
|
[include 8_1_http_message.qbk]
|
||||||
[include 6_2_http_comparison.qbk]
|
[include 8_2_http_comparison.qbk]
|
||||||
[include 6_3_websocket_zaphoyd.qbk]
|
[include 8_3_websocket_zaphoyd.qbk]
|
||||||
[include 6_4_faq.qbk]
|
[include 8_4_faq.qbk]
|
||||||
|
|
||||||
[endsect]
|
[endsect]
|
@@ -5,7 +5,7 @@
|
|||||||
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
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
|
To set realistic expectations and prevent a litany of duplicate review
|
||||||
statements, these notes address the most common questions and comments
|
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.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>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.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
|
<include>$(broot)/tools/boostbook/dtd
|
||||||
:
|
:
|
||||||
<location>temp
|
<location>temp
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
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
|
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
|
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
|
If present, indicates that the body can hold a message body
|
||||||
parsing result. The type must meet the requirements of
|
parsing result. The type must meet the requirements of
|
||||||
[link beast.ref.BodyWriter [*BodyWriter]]. The implementation
|
__BodyWriter__. The implementation constructs an object of
|
||||||
constructs an object of this type to obtain buffers into which
|
this type to obtain buffers into which parsed body octets
|
||||||
parsed body octets are placed.
|
are placed.
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
[
|
[
|
||||||
[`X::reader`]
|
[`X::reader`]
|
||||||
[]
|
[]
|
||||||
[
|
[
|
||||||
If present, indicates that the body is serializable.
|
If present, indicates that the body is serializable. The type
|
||||||
The type must meet the requirements of
|
must meet the requirements of __BodyReader__. The implementation
|
||||||
[link beast.ref.BodyReader [*BodyReader]]. The implementation
|
|
||||||
constructs an object of this type to obtain buffers representing
|
constructs an object of this type to obtain buffers representing
|
||||||
the message body for serialization.
|
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)
|
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
|
A [*BodyReader] provides an online algorithm to obtain a sequence of zero
|
||||||
or more buffers from a body during serialization. The implementation creates
|
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)
|
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
|
A [*BodyWriter] provides an online algorithm to transfer a series of zero
|
||||||
or more buffers containing parsed body octets into a message container. The
|
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)
|
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__
|
* __ConstBufferSequence__
|
||||||
* __MutableBufferSequence__
|
* __MutableBufferSequence__
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
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
|
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
|
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)
|
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.
|
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)
|
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
|
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.
|
these requirements become serializable by the implementation.
|
||||||
|
|
||||||
In this table:
|
In this table:
|
||||||
@@ -23,7 +23,8 @@ In this table:
|
|||||||
[`X::value_type`]
|
[`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
|
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],
|
satisfies all the requirements of [*ForwardIterator],
|
||||||
except that:
|
except that:
|
||||||
|
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
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
|
Stream types represent objects capable of performing synchronous or
|
||||||
asynchronous I/O. They are based on concepts from `boost::asio`.
|
asynchronous I/O. They are based on concepts from `boost::asio`.
|
||||||
|
@@ -99,11 +99,11 @@
|
|||||||
</simplelist>
|
</simplelist>
|
||||||
<bridgehead renderas="sect3">Concepts</bridgehead>
|
<bridgehead renderas="sect3">Concepts</bridgehead>
|
||||||
<simplelist type="vert" columns="1">
|
<simplelist type="vert" columns="1">
|
||||||
<member><link linkend="beast.ref.Body">Body</link></member>
|
<member><link linkend="beast.concept.Body">Body</link></member>
|
||||||
<member><link linkend="beast.ref.BodyReader">BodyReader</link></member>
|
<member><link linkend="beast.concept.BodyReader">BodyReader</link></member>
|
||||||
<member><link linkend="beast.ref.BodyWriter">BodyWriter</link></member>
|
<member><link linkend="beast.concept.BodyWriter">BodyWriter</link></member>
|
||||||
<member><link linkend="beast.ref.Field">Field</link></member>
|
<member><link linkend="beast.concept.Field">Field</link></member>
|
||||||
<member><link linkend="beast.ref.FieldSequence">FieldSequence</link></member>
|
<member><link linkend="beast.concept.FieldSequence">FieldSequence</link></member>
|
||||||
</simplelist>
|
</simplelist>
|
||||||
</entry>
|
</entry>
|
||||||
<entry valign="top">
|
<entry valign="top">
|
||||||
@@ -223,11 +223,11 @@
|
|||||||
<entry valign="top">
|
<entry valign="top">
|
||||||
<bridgehead renderas="sect3">Concepts</bridgehead>
|
<bridgehead renderas="sect3">Concepts</bridgehead>
|
||||||
<simplelist type="vert" columns="1">
|
<simplelist type="vert" columns="1">
|
||||||
<member><link linkend="beast.ref.streams.AsyncStream">AsyncStream</link></member>
|
<member><link linkend="beast.concept.streams.AsyncStream">AsyncStream</link></member>
|
||||||
<member><link linkend="beast.ref.BufferSequence">BufferSequence</link></member>
|
<member><link linkend="beast.concept.BufferSequence">BufferSequence</link></member>
|
||||||
<member><link linkend="beast.ref.DynamicBuffer">DynamicBuffer</link></member>
|
<member><link linkend="beast.concept.DynamicBuffer">DynamicBuffer</link></member>
|
||||||
<member><link linkend="beast.ref.streams.Stream">Stream</link></member>
|
<member><link linkend="beast.concept.streams.Stream">Stream</link></member>
|
||||||
<member><link linkend="beast.ref.streams.SyncStream">SyncStream</link></member>
|
<member><link linkend="beast.concept.streams.SyncStream">SyncStream</link></member>
|
||||||
</simplelist>
|
</simplelist>
|
||||||
</entry>
|
</entry>
|
||||||
<entry valign="top">
|
<entry valign="top">
|
||||||
|
@@ -1609,7 +1609,7 @@
|
|||||||
<xsl:text> </xsl:text>
|
<xsl:text> </xsl:text>
|
||||||
<xsl:choose>
|
<xsl:choose>
|
||||||
<xsl:when test="type = 'class AsyncStream'">
|
<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>
|
||||||
<xsl:when test="type = 'class AsyncReadStream'">
|
<xsl:when test="type = 'class AsyncReadStream'">
|
||||||
<xsl:text>class __AsyncReadStream__</xsl:text>
|
<xsl:text>class __AsyncReadStream__</xsl:text>
|
||||||
@@ -1618,14 +1618,14 @@
|
|||||||
<xsl:text>class __AsyncWriteStream__</xsl:text>
|
<xsl:text>class __AsyncWriteStream__</xsl:text>
|
||||||
</xsl:when>
|
</xsl:when>
|
||||||
<xsl:when test="type = 'class Body'">
|
<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>
|
||||||
<xsl:when test="type = 'class BufferSequence'">
|
<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>
|
||||||
<xsl:when test="(type = 'class' or type = 'class...') and declname = 'BufferSequence'">
|
<xsl:when test="(type = 'class' or type = 'class...') and declname = 'BufferSequence'">
|
||||||
<xsl:value-of select="type"/>
|
<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>
|
||||||
<xsl:when test="declname = 'CompletionHandler' or type = 'class CompletionHandler'">
|
<xsl:when test="declname = 'CompletionHandler' or type = 'class CompletionHandler'">
|
||||||
<xsl:text>class __CompletionHandler__</xsl:text>
|
<xsl:text>class __CompletionHandler__</xsl:text>
|
||||||
@@ -1634,7 +1634,7 @@
|
|||||||
<xsl:text>class __ConstBufferSequence__</xsl:text>
|
<xsl:text>class __ConstBufferSequence__</xsl:text>
|
||||||
</xsl:when>
|
</xsl:when>
|
||||||
<xsl:when test="declname = 'DynamicBuffer' or type = 'class DynamicBuffer'">
|
<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>
|
||||||
<xsl:when test="declname = 'Handler' or type = 'class Handler'">
|
<xsl:when test="declname = 'Handler' or type = 'class Handler'">
|
||||||
<xsl:text>class __Handler__</xsl:text>
|
<xsl:text>class __Handler__</xsl:text>
|
||||||
@@ -1642,14 +1642,11 @@
|
|||||||
<xsl:when test="declname = 'MutableBufferSequence' or type = 'class MutableBufferSequence'">
|
<xsl:when test="declname = 'MutableBufferSequence' or type = 'class MutableBufferSequence'">
|
||||||
<xsl:text>class __MutableBufferSequence__</xsl:text>
|
<xsl:text>class __MutableBufferSequence__</xsl:text>
|
||||||
</xsl:when>
|
</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: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>
|
||||||
<xsl:when test="type = 'class SyncStream'">
|
<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>
|
||||||
<xsl:when test="declname = 'SyncReadStream' or type = 'class SyncReadStream'">
|
<xsl:when test="declname = 'SyncReadStream' or type = 'class SyncReadStream'">
|
||||||
<xsl:text>class __SyncReadStream__</xsl:text>
|
<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 <memory>
|
||||||
#include <utility>
|
#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.
|
// This composed operation reads a line of input and echoes it back.
|
||||||
//
|
//
|
||||||
template<class AsyncStream, class Handler>
|
template<class AsyncStream, class Handler>
|
||||||
@@ -127,9 +139,10 @@ public:
|
|||||||
void operator()(beast::error_code ec, std::size_t bytes_transferred);
|
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>
|
template<class AsyncStream, class Handler>
|
||||||
void echo_op<AsyncStream, Handler>::
|
void echo_op<AsyncStream, Handler>::
|
||||||
operator()(beast::error_code ec, std::size_t bytes_transferred)
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//]
|
||||||
|
|
||||||
|
//[core_sample_echo_op_2
|
||||||
|
|
||||||
|
template<class AsyncStream, class Handler>
|
||||||
|
class echo_op;
|
||||||
|
|
||||||
// Read a line and echo it back
|
// Read a line and echo it back
|
||||||
//
|
//
|
||||||
template<class AsyncStream, class CompletionToken>
|
template<class AsyncStream, class CompletionToken>
|
||||||
@@ -205,6 +225,8 @@ async_echo(AsyncStream& stream, CompletionToken&& token)
|
|||||||
return init.result.get();
|
return init.result.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//]
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
using address_type = boost::asio::ip::address;
|
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)
|
// 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/core.hpp>
|
||||||
#include <beast/http.hpp>
|
#include <beast/http.hpp>
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
@@ -39,3 +41,5 @@ int main()
|
|||||||
beast::http::read(sock, b, res);
|
beast::http::read(sock, b, res);
|
||||||
std::cout << res << std::endl;
|
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)
|
// 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/core.hpp>
|
||||||
#include <beast/websocket.hpp>
|
#include <beast/websocket.hpp>
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
@@ -33,3 +35,5 @@ int main()
|
|||||||
ws.close(beast::websocket::close_code::normal);
|
ws.close(beast::websocket::close_code::normal);
|
||||||
std::cout << beast::buffers(b.data()) << "\n";
|
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
|
@ref verb::unknown is returned. Callers may use @ref method_string
|
||||||
to retrieve the exact text.
|
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
|
@see @ref method_string
|
||||||
*/
|
*/
|
||||||
@@ -148,7 +148,7 @@ struct header<true, Fields>
|
|||||||
|
|
||||||
/** Return the request-method string.
|
/** 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
|
@see @ref method
|
||||||
*/
|
*/
|
||||||
@@ -166,7 +166,7 @@ struct header<true, Fields>
|
|||||||
|
|
||||||
@param s A string representing the request-method.
|
@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
|
void
|
||||||
method(string_view s)
|
method(string_view s)
|
||||||
@@ -176,7 +176,7 @@ struct header<true, Fields>
|
|||||||
|
|
||||||
/** Returns the request-target string.
|
/** 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
|
string_view
|
||||||
target() const
|
target() const
|
||||||
@@ -188,7 +188,7 @@ struct header<true, Fields>
|
|||||||
|
|
||||||
@param s A string representing the request-target.
|
@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
|
void
|
||||||
target(string_view s)
|
target(string_view s)
|
||||||
@@ -295,7 +295,7 @@ struct header<false, Fields>
|
|||||||
function returns @ref status::unknown. Use @ref result_int
|
function returns @ref status::unknown. Use @ref result_int
|
||||||
to return the raw status code as a number.
|
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
|
status
|
||||||
result() const
|
result() const
|
||||||
@@ -308,7 +308,7 @@ struct header<false, Fields>
|
|||||||
|
|
||||||
@param v The code to set.
|
@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
|
void
|
||||||
result(status v)
|
result(status v)
|
||||||
@@ -337,7 +337,7 @@ struct header<false, Fields>
|
|||||||
This returns the raw status code as an integer, even
|
This returns the raw status code as an integer, even
|
||||||
when that code is not in the list of known status codes.
|
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
|
int
|
||||||
result_int() const
|
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
|
string_view
|
||||||
reason() const
|
reason() const
|
||||||
@@ -358,7 +358,7 @@ struct header<false, Fields>
|
|||||||
return get_reason();
|
return get_reason();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Set the Reason-Phrase
|
/** Set the response reason-phrase (deprecated)
|
||||||
|
|
||||||
This function sets a custom reason-phrase to a copy of
|
This function sets a custom reason-phrase to a copy of
|
||||||
the string passed in. Normally it is not necessary to set
|
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
|
string. This will restore the default standard reason text
|
||||||
based on the status code used when serializing.
|
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
|
void
|
||||||
reason(string_view s)
|
reason(string_view s)
|
||||||
|
@@ -43,7 +43,7 @@ unit-test http-tests :
|
|||||||
../extras/beast/unit_test/main.cpp
|
../extras/beast/unit_test/main.cpp
|
||||||
http/basic_parser.cpp
|
http/basic_parser.cpp
|
||||||
http/buffer_body.cpp
|
http/buffer_body.cpp
|
||||||
http/design.cpp
|
http/doc_http_samples.cpp
|
||||||
http/dynamic_body.cpp
|
http/dynamic_body.cpp
|
||||||
http/error.cpp
|
http/error.cpp
|
||||||
http/fields.cpp
|
http/fields.cpp
|
||||||
|
@@ -1,18 +1,20 @@
|
|||||||
# Part of Beast
|
# Part of Beast
|
||||||
|
|
||||||
|
GroupSources(examples examples)
|
||||||
GroupSources(extras/beast extras)
|
GroupSources(extras/beast extras)
|
||||||
GroupSources(include/beast beast)
|
GroupSources(include/beast beast)
|
||||||
GroupSources(test/http "/")
|
GroupSources(test/http "/")
|
||||||
|
|
||||||
add_executable (http-tests
|
add_executable (http-tests
|
||||||
${BEAST_INCLUDES}
|
${BEAST_INCLUDES}
|
||||||
|
${EXAMPLES_INCLUDES}
|
||||||
${EXTRAS_INCLUDES}
|
${EXTRAS_INCLUDES}
|
||||||
message_fuzz.hpp
|
message_fuzz.hpp
|
||||||
test_parser.hpp
|
test_parser.hpp
|
||||||
../../extras/beast/unit_test/main.cpp
|
../../extras/beast/unit_test/main.cpp
|
||||||
basic_parser.cpp
|
basic_parser.cpp
|
||||||
buffer_body.cpp
|
buffer_body.cpp
|
||||||
design.cpp
|
doc_http_samples.cpp
|
||||||
dynamic_body.cpp
|
dynamic_body.cpp
|
||||||
empty_body.cpp
|
empty_body.cpp
|
||||||
error.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