mirror of
https://github.com/boostorg/beast.git
synced 2025-07-30 21:07:26 +02:00
Documentation work
This commit is contained in:
@ -5,6 +5,7 @@ Version 45
|
|||||||
* Better test::enable_yield_to
|
* Better test::enable_yield_to
|
||||||
* Add test::pipe
|
* Add test::pipe
|
||||||
* Fix header::reason
|
* Fix header::reason
|
||||||
|
* Documentation work
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
602
doc/design.qbk
602
doc/design.qbk
@ -9,9 +9,10 @@
|
|||||||
|
|
||||||
[block '''
|
[block '''
|
||||||
<informaltable frame="all"><tgroup cols="1"><colspec colname="a"/><tbody><row><entry valign="top"><simplelist>
|
<informaltable frame="all"><tgroup cols="1"><colspec colname="a"/><tbody><row><entry valign="top"><simplelist>
|
||||||
<member><link linkend="beast.design.http">HTTP FAQ</link></member>
|
<member><link linkend="beast.design.http_message">HTTP Message Container</link></member>
|
||||||
<member><link linkend="beast.design.websocket">WebSocket FAQ</link></member>
|
<member><link linkend="beast.design.http_comparison">HTTP Comparison to Other Libraries</link></member>
|
||||||
<member><link linkend="beast.design.websocketpp">Comparison to Zaphoyd Studios WebSocket++</link></member>
|
<member><link linkend="beast.design.websocket_zaphoyd">Comparison to Zaphoyd Studios WebSocket++</link></member>
|
||||||
|
<member><link linkend="beast.design.review">Boost Formal Review FAQ</link></member>
|
||||||
</simplelist></entry></row></tbody></tgroup></informaltable>
|
</simplelist></entry></row></tbody></tgroup></informaltable>
|
||||||
''']
|
''']
|
||||||
|
|
||||||
@ -46,27 +47,6 @@ of other packages offering similar functionality. In this section and the
|
|||||||
FAQs that follow we attempt to answer those questions that are also applicable
|
FAQs that follow we attempt to answer those questions that are also applicable
|
||||||
to Beast.
|
to Beast.
|
||||||
|
|
||||||
[variablelist
|
|
||||||
[[
|
|
||||||
"I would also like to see instances of this library being used
|
|
||||||
in production. That would give some evidence that the design
|
|
||||||
works in practice."
|
|
||||||
][
|
|
||||||
Beast.HTTP and Beast.WebSocket are production ready and currently
|
|
||||||
running on public servers receiving traffic and handling millions of
|
|
||||||
dollars worth of financial transactions daily. The servers run [*rippled],
|
|
||||||
open source software ([@https://github.com/ripple/rippled repository])
|
|
||||||
implementing the
|
|
||||||
[@https://ripple.com/files/ripple_consensus_whitepaper.pdf [*Ripple Consensus Protocol]],
|
|
||||||
technology provided by [@http://ripple.com Ripple].
|
|
||||||
]]
|
|
||||||
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[section:http HTTP FAQ]
|
|
||||||
|
|
||||||
For HTTP we model the message to maximize flexibility of implementation
|
For HTTP we model the message to maximize flexibility of implementation
|
||||||
strategies while allowing familiar verbs such as [*`read`] and [*`write`].
|
strategies while allowing familiar verbs such as [*`read`] and [*`write`].
|
||||||
The HTTP interface is further driven by the needs of the WebSocket module,
|
The HTTP interface is further driven by the needs of the WebSocket module,
|
||||||
@ -79,575 +59,9 @@ start. Other design goals:
|
|||||||
|
|
||||||
* Allow for customizations, if the user needs it.
|
* Allow for customizations, if the user needs it.
|
||||||
|
|
||||||
[variablelist
|
[include design/http_message.qbk]
|
||||||
|
[include design/http_comparison.qbk]
|
||||||
[[
|
[include design/websocket_zaphoyd.qbk]
|
||||||
"Some more advanced examples, e.g. including TLS with client/server
|
[include design/review.qbk]
|
||||||
certificates would help."
|
|
||||||
][
|
|
||||||
The HTTP interface doesn't try to reinvent the wheel, it just uses
|
|
||||||
the `boost::asio::ip::tcp::socket` or `boost::asio::ssl::stream` that
|
|
||||||
you set up beforehand. Callers use the interfaces already existing
|
|
||||||
on those objects to make outgoing connections, accept incoming connections,
|
|
||||||
or establish TLS sessions with certificates. We find the available Asio
|
|
||||||
examples for performing these tasks sufficient.
|
|
||||||
]]
|
|
||||||
|
|
||||||
[[
|
|
||||||
"A built-in HTTP router?"
|
|
||||||
][
|
|
||||||
We presume this means a facility to match expressions against the URI
|
|
||||||
in HTTP requests, and dispatch them to calling code. The authors feel
|
|
||||||
that this is a responsibility of higher level code. Beast does
|
|
||||||
not try to offer a web server.
|
|
||||||
]]
|
|
||||||
|
|
||||||
[[
|
|
||||||
"HTTP Cookies? Forms/File Uploads?"
|
|
||||||
][
|
|
||||||
Cookies, or managing these types of HTTP headers in general, is the
|
|
||||||
responsibility of higher levels. Beast.HTTP just tries to get complete
|
|
||||||
messages to and from the calling code. It deals in the HTTP headers just
|
|
||||||
enough to process the message body and leaves the rest to callers. However,
|
|
||||||
for forms and file uploads the symmetric interface of the message class
|
|
||||||
allows HTTP requests to include arbitrary body types including those needed
|
|
||||||
to upload a file or fill out a form.
|
|
||||||
]]
|
|
||||||
|
|
||||||
[[
|
|
||||||
"...supporting TLS (is this a feature? If not this would be a show-stopper),
|
|
||||||
etc."
|
|
||||||
][
|
|
||||||
Beast does not provide direct facilities for implementing TLS connections;
|
|
||||||
however, the interfaces already existing on the `boost::asio::ssl::stream`
|
|
||||||
are available and can be used to establish secure connections. Then,
|
|
||||||
functions like `http::read` or `http::async_write` can work with those
|
|
||||||
encrypted connections with no problem.
|
|
||||||
]]
|
|
||||||
|
|
||||||
[[
|
|
||||||
"There should also be more examples of how to integrate the http service
|
|
||||||
with getting files from the file system, generating responses CGI-style"
|
|
||||||
][
|
|
||||||
The design goal for the library is to not try to invent a web server.
|
|
||||||
We feel that there is a strong need for a basic implementation that
|
|
||||||
models the HTTP message and provides functions to send and receive them
|
|
||||||
over Asio. Such an implementation should serve as a building block upon
|
|
||||||
which higher abstractions such as the aforementioned HTTP service or
|
|
||||||
cgi-gateway can be built.
|
|
||||||
|
|
||||||
One of the example programs implements a simple HTTP server that
|
|
||||||
delivers files from the filesystem.
|
|
||||||
]]
|
|
||||||
|
|
||||||
[[
|
|
||||||
"You should send a 100-continue to ask for the rest of the body if required."
|
|
||||||
][
|
|
||||||
The Beast interface supporst this functionality (by allowing this
|
|
||||||
special case of partial message parsing and serialization). Specifically,
|
|
||||||
it lets callers read the request up to just before the body,
|
|
||||||
and let callers write the request up to just before the body. However,
|
|
||||||
making use of this behavior is up to callers (since Beast is low level).
|
|
||||||
]]
|
|
||||||
|
|
||||||
[[
|
|
||||||
"What about HTTP/2?"
|
|
||||||
][
|
|
||||||
Many reviewers feel that HTTP/2 support is an essential feature of
|
|
||||||
a HTTP library. The authors agree that HTTP/2 is important but also
|
|
||||||
feel that the most sensible implementation is one that does not re-use
|
|
||||||
the same network reading and writing interface for 2 as that for 1.0
|
|
||||||
and 1.1.
|
|
||||||
|
|
||||||
The Beast HTTP message model is suitable for HTTP/2 and can be re-used.
|
|
||||||
The IETF HTTP Working Group adopted message compatiblity with HTTP/1.x
|
|
||||||
as an explicit goal. A parser can simply emit full headers after
|
|
||||||
decoding the compressed HTTP/2 headers. The stream ID is not logically
|
|
||||||
part of the message but rather message metadata and should be
|
|
||||||
communicated out-of-band (see below). HTTP/2 sessions begin with a
|
|
||||||
traditional HTTP/1.1 Upgrade similar in fashion to the WebSocket
|
|
||||||
upgrade. An HTTP/2 implementation can use existing Beast.HTTP primitives
|
|
||||||
to perform this handshake.
|
|
||||||
|
|
||||||
Free functions for HTTP/2 sessions are not possible because of the
|
|
||||||
requirement to maintain per-session state. For example, to decode the
|
|
||||||
compressed headers. Or to remember and respect the remote peer's window
|
|
||||||
settings. The authors propose that a HTTP/2 implementation be written
|
|
||||||
as a separate class template, similar to the `websocket::stream` but with
|
|
||||||
additional interfaces to support version 2 features. We feel that
|
|
||||||
Beast.HTTP offers enough useful functionality to justify inclusion,
|
|
||||||
so that developers can take advantage of it right away instead of
|
|
||||||
waiting.
|
|
||||||
]]
|
|
||||||
|
|
||||||
]
|
|
||||||
|
|
||||||
[endsect]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[section:websocket WebSocket FAQ]
|
|
||||||
|
|
||||||
[variablelist
|
|
||||||
|
|
||||||
[[
|
|
||||||
What about message compression?
|
|
||||||
][
|
|
||||||
Beast WebSocket supports the permessage-deflate extension described in
|
|
||||||
[@https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-00 draft-ietf-hybi-permessage-compression-00].
|
|
||||||
The library comes with a header-only, C++11 port of ZLib's "deflate" codec
|
|
||||||
used in the implementation of the permessage-deflate extension.
|
|
||||||
]]
|
|
||||||
|
|
||||||
[[
|
|
||||||
Where is the TLS/SSL interface?
|
|
||||||
][
|
|
||||||
The `websocket::stream` wraps the socket or stream that you provide
|
|
||||||
(for example, a `boost::asio::ip::tcp::socket` or a
|
|
||||||
`boost::asio::ssl::stream`). You establish your TLS connection using the
|
|
||||||
interface on `ssl::stream` like shown in all of the Asio examples, then
|
|
||||||
construct your `websocket::stream` around it. It works perfectly fine;
|
|
||||||
Beast.WebSocket doesn't try to reinvent the wheel or put a fresh coat of
|
|
||||||
interface paint on the `ssl::stream`.
|
|
||||||
|
|
||||||
The WebSocket implementation [*does] provide support for shutting down
|
|
||||||
the TLS connection through the use of the ADL compile-time virtual functions
|
|
||||||
[link beast.ref.websocket__teardown `teardown`] and
|
|
||||||
[link beast.ref.websocket__async_teardown `async_teardown`]. These will
|
|
||||||
properly close the connection as per rfc6455 and overloads are available
|
|
||||||
for TLS streams. Callers may provide their own overloads of these functions
|
|
||||||
for user-defined next layer types.
|
|
||||||
]]
|
|
||||||
|
|
||||||
]
|
|
||||||
|
|
||||||
[endsect]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[section:websocketpp Comparison to Zaphoyd Studios WebSocket++]
|
|
||||||
|
|
||||||
[variablelist
|
|
||||||
|
|
||||||
[[
|
|
||||||
How does this compare to [@https://www.zaphoyd.com/websocketpp websocketpp],
|
|
||||||
an alternate header-only WebSocket implementation?
|
|
||||||
][
|
|
||||||
[variablelist
|
|
||||||
|
|
||||||
[[1. Synchronous Interface][
|
|
||||||
|
|
||||||
Beast offers full support for WebSockets using a synchronous interface. It
|
|
||||||
uses the same style of interfaces found in Boost.Asio: versions that throw
|
|
||||||
exceptions, or versions that return the error code in a reference parameter:
|
|
||||||
|
|
||||||
[table
|
|
||||||
[
|
|
||||||
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L774 Beast]]
|
|
||||||
[websocketpp]
|
|
||||||
][
|
|
||||||
[```
|
|
||||||
template<class DynamicBuffer>
|
|
||||||
void
|
|
||||||
read(opcode& op, DynamicBuffer& dynabuf)
|
|
||||||
```]
|
|
||||||
[
|
|
||||||
/<not available>/
|
|
||||||
]
|
|
||||||
]]]]
|
|
||||||
|
|
||||||
[[2. Connection Model][
|
|
||||||
|
|
||||||
websocketpp supports multiple transports by utilizing a trait, the `config::transport_type`
|
|
||||||
([@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/asio/connection.hpp#L60 asio transport example])
|
|
||||||
To get an idea of the complexity involved with implementing a transport,
|
|
||||||
compare the asio transport to the
|
|
||||||
[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/iostream/connection.hpp#L59 `iostream` transport]
|
|
||||||
(a layer that allows websocket communication over a `std::iostream`).
|
|
||||||
|
|
||||||
In contrast, Beast abstracts the transport by defining just one [*`NextLayer`]
|
|
||||||
template argument The type requirements for [*`NextLayer`] are
|
|
||||||
already familiar to users as they are documented in Asio:
|
|
||||||
__AsyncReadStream__, __AsyncWriteStream__, __SyncReadStream__, __SyncWriteStream__.
|
|
||||||
|
|
||||||
The type requirements for instantiating `beast::websocket::stream` versus
|
|
||||||
`websocketpp::connection` with user defined types are vastly reduced
|
|
||||||
(18 functions versus 2). Note that websocketpp connections are passed by
|
|
||||||
`shared_ptr`. Beast does not use `shared_ptr` anywhere in its public interface.
|
|
||||||
A `beast::websocket::stream` is constructible and movable in a manner identical
|
|
||||||
to a `boost::asio::ip::tcp::socket`. Callers can put such objects in a
|
|
||||||
`shared_ptr` if they want to, but there is no requirement to do so.
|
|
||||||
|
|
||||||
[table
|
|
||||||
[
|
|
||||||
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp Beast]]
|
|
||||||
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L234 websocketpp]]
|
|
||||||
][
|
|
||||||
[```
|
|
||||||
template<class NextLayer>
|
|
||||||
class stream
|
|
||||||
{
|
|
||||||
NextLayer next_layer_;
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```]
|
|
||||||
[```
|
|
||||||
template <typename config>
|
|
||||||
class connection
|
|
||||||
: public config::transport_type::transport_con_type
|
|
||||||
, public config::connection_base
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
typedef lib::shared_ptr<type> ptr;
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```]
|
|
||||||
]]]]
|
|
||||||
|
|
||||||
[[3. Client and Server Role][
|
|
||||||
|
|
||||||
websocketpp provides multi-role support through a hierarchy of
|
|
||||||
different classes. A `beast::websocket::stream` is role-agnostic, it
|
|
||||||
offers member functions to perform both client and server handshakes
|
|
||||||
in the same class. The same types are used for client and server
|
|
||||||
streams.
|
|
||||||
|
|
||||||
[table
|
|
||||||
[
|
|
||||||
[Beast]
|
|
||||||
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/roles/server_endpoint.hpp#L39 websocketpp],
|
|
||||||
[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/roles/client_endpoint.hpp#L42 also]]
|
|
||||||
][
|
|
||||||
[
|
|
||||||
/<not needed>/
|
|
||||||
]
|
|
||||||
[```
|
|
||||||
template <typename config>
|
|
||||||
class client : public endpoint<connection<config>,config>;
|
|
||||||
template <typename config>
|
|
||||||
class server : public endpoint<connection<config>,config>;
|
|
||||||
```]
|
|
||||||
]]]]
|
|
||||||
|
|
||||||
[[4. Thread Safety][
|
|
||||||
|
|
||||||
websocketpp uses mutexes to protect shared data from concurrent
|
|
||||||
access. In contrast, Beast does not use mutexes anywhere in its
|
|
||||||
implementation. Instead, it follows the Asio pattern. Calls to
|
|
||||||
asynchronous initiation functions use the same method to invoke
|
|
||||||
intermediate handlers as the method used to invoke the final handler,
|
|
||||||
through the __asio_handler_invoke__ mechanism.
|
|
||||||
|
|
||||||
The only requirement in Beast is that calls to asynchronous initiation
|
|
||||||
functions are made from the same implicit or explicit strand. For
|
|
||||||
example, if the `io_service` associated with a `beast::websocket::stream`
|
|
||||||
is single threaded, this counts as an implicit strand and no performance
|
|
||||||
costs associated with mutexes are incurred.
|
|
||||||
|
|
||||||
[table
|
|
||||||
[
|
|
||||||
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/impl/read_frame_op.ipp#L118 Beast]]
|
|
||||||
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/iostream/connection.hpp#L706 websocketpp]]
|
|
||||||
][
|
|
||||||
[```
|
|
||||||
template <class Function>
|
|
||||||
friend
|
|
||||||
void asio_handler_invoke(Function&& f, read_frame_op* op)
|
|
||||||
{
|
|
||||||
return boost_asio_handler_invoke_helpers::invoke(f, op->d_->h);
|
|
||||||
}
|
|
||||||
```]
|
|
||||||
[```
|
|
||||||
mutex_type m_read_mutex;
|
|
||||||
```]
|
|
||||||
]]]]
|
|
||||||
|
|
||||||
[[5. Callback Model][
|
|
||||||
|
|
||||||
websocketpp requires a one-time call to set the handler for each event
|
|
||||||
in its interface (for example, upon message receipt). The handler is
|
|
||||||
represented by a `std::function` equivalent. Its important to recognize
|
|
||||||
that the websocketpp interface performs type-erasure on this handler.
|
|
||||||
|
|
||||||
In comparison, Beast handlers are specified in a manner identical to
|
|
||||||
Boost.Asio. They are function objects which can be copied or moved but
|
|
||||||
most importantly they are not type erased. The compiler can see
|
|
||||||
through the type directly to the implementation, permitting
|
|
||||||
optimization. Furthermore, Beast follows the Asio rules for treatment
|
|
||||||
of handlers. It respects any allocation, continuation, or invocation
|
|
||||||
customizations associated with the handler through the use of argument
|
|
||||||
dependent lookup overloads of functions such as `asio_handler_allocate`.
|
|
||||||
|
|
||||||
The Beast completion handler is provided at the call site. For each
|
|
||||||
call to an asynchronous initiation function, it is guaranteed that
|
|
||||||
there will be exactly one final call to the handler. This functions
|
|
||||||
exactly the same way as the asynchronous initiation functions found in
|
|
||||||
Boost.Asio, allowing the composition of higher level abstractions.
|
|
||||||
|
|
||||||
[table
|
|
||||||
[
|
|
||||||
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L834 Beast]]
|
|
||||||
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L281 websocketpp],
|
|
||||||
[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L473 also]]
|
|
||||||
][
|
|
||||||
[```
|
|
||||||
template<class DynamicBuffer, class ReadHandler>
|
|
||||||
typename async_completion<ReadHandler, void(error_code)>::result_type
|
|
||||||
async_read(opcode& op, DynamicBuffer& dynabuf, ReadHandler&& handler);
|
|
||||||
```]
|
|
||||||
[```
|
|
||||||
typedef lib::function<void(connection_hdl,message_ptr)> message_handler;
|
|
||||||
void set_message_handler(message_handler h);
|
|
||||||
```]
|
|
||||||
]]]]
|
|
||||||
|
|
||||||
[[6. Extensible Asynchronous Model][
|
|
||||||
|
|
||||||
Beast fully supports the
|
|
||||||
[@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3896.pdf Extensible Asynchronous Model]
|
|
||||||
developed by Christopher Kohlhoff, author of Boost.Asio (see Section 8).
|
|
||||||
|
|
||||||
Beast websocket asynchronous interfaces may be used seamlessly with
|
|
||||||
`std::future` stackful/stackless coroutines, or user defined customizations.
|
|
||||||
|
|
||||||
[table
|
|
||||||
[
|
|
||||||
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/impl/stream.ipp#L378 Beast]]
|
|
||||||
[websocketpp]
|
|
||||||
][
|
|
||||||
[```
|
|
||||||
beast::async_completion<ReadHandler, void(error_code)> completion(handler);
|
|
||||||
read_op<DynamicBuffer, decltype(completion.handler)>{
|
|
||||||
completion.handler, *this, op, buffer};
|
|
||||||
return completion.result.get();
|
|
||||||
```]
|
|
||||||
[
|
|
||||||
/<not available>/
|
|
||||||
]
|
|
||||||
]]]]
|
|
||||||
|
|
||||||
[[7. Message Buffering][
|
|
||||||
|
|
||||||
websocketpp defines a message buffer, passed in arguments by
|
|
||||||
`shared_ptr`, and an associated message manager which permits
|
|
||||||
aggregation and reuse of memory. The implementation of
|
|
||||||
`websocketpp::message` uses a `std::string` to hold the payload. If an
|
|
||||||
incoming message is broken up into multiple frames, the string may be
|
|
||||||
reallocated for each continuation frame. The `std::string` always uses
|
|
||||||
the standard allocator, it is not possible to customize the choice of
|
|
||||||
allocator.
|
|
||||||
|
|
||||||
Beast allows callers to specify the object for receiving the message
|
|
||||||
or frame data, which is of any type meeting the requirements of
|
|
||||||
__DynamicBuffer__ (modeled after `boost::asio::streambuf`).
|
|
||||||
|
|
||||||
Beast comes with the class __basic_multi_buffer__, an efficient
|
|
||||||
implementation of the __DynamicBuffer__ concept which makes use of multiple
|
|
||||||
allocated octet arrays. If an incoming message is broken up into
|
|
||||||
multiple pieces, no reallocation occurs. Instead, new allocations are
|
|
||||||
appended to the sequence when existing allocations are filled. Beast
|
|
||||||
does not impose any particular memory management model on callers. The
|
|
||||||
__basic_multi_buffer__ provided by beast supports standard allocators through
|
|
||||||
a template argument. Use the __DynamicBuffer__ that comes with beast,
|
|
||||||
customize the allocator if you desire, or provide your own type that
|
|
||||||
meets the requirements.
|
|
||||||
|
|
||||||
[table
|
|
||||||
[
|
|
||||||
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L774 Beast]]
|
|
||||||
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/message_buffer/message.hpp#L78 websocketpp]]
|
|
||||||
][
|
|
||||||
[```
|
|
||||||
template<class DynamicBuffer>
|
|
||||||
read(opcode& op, DynamicBuffer& dynabuf);
|
|
||||||
```]
|
|
||||||
[```
|
|
||||||
template <template<class> class con_msg_manager>
|
|
||||||
class message {
|
|
||||||
public:
|
|
||||||
typedef lib::shared_ptr<message> ptr;
|
|
||||||
...
|
|
||||||
std::string m_payload;
|
|
||||||
...
|
|
||||||
};
|
|
||||||
```]
|
|
||||||
]]]]
|
|
||||||
|
|
||||||
[[8. Sending Messages][
|
|
||||||
|
|
||||||
When sending a message, websocketpp requires that the payload is
|
|
||||||
packaged in a `websocketpp::message` object using `std::string` as the
|
|
||||||
storage, or it requires a copy of the caller provided buffer by
|
|
||||||
constructing a new message object. Messages are placed onto an
|
|
||||||
outgoing queue. An asynchronous write operation runs in the background
|
|
||||||
to clear the queue. No user facing handler can be registered to be
|
|
||||||
notified when messages or frames have completed sending.
|
|
||||||
|
|
||||||
Beast doesn't allocate or make copies of buffers when sending data. The
|
|
||||||
caller's buffers are sent in-place. You can use any object meeting the
|
|
||||||
requirements of
|
|
||||||
[@http://www.boost.org/doc/html/boost_asio/reference/ConstBufferSequence.html ConstBufferSequence],
|
|
||||||
permitting efficient scatter-gather I/O.
|
|
||||||
|
|
||||||
The [*ConstBufferSequence] interface allows callers to send data from
|
|
||||||
memory-mapped regions (not possible in websocketpp). Callers can also
|
|
||||||
use the same buffers to send data to multiple streams, for example
|
|
||||||
broadcasting common subscription data to many clients at once. For
|
|
||||||
each call to `async_write` the completion handler is called once when
|
|
||||||
the data finishes sending, in a manner identical to `boost::asio::async_write`.
|
|
||||||
|
|
||||||
[table
|
|
||||||
[
|
|
||||||
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L1048 Beast]]
|
|
||||||
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L672 websocketpp]]
|
|
||||||
][
|
|
||||||
[```
|
|
||||||
template<class ConstBufferSequence>
|
|
||||||
void
|
|
||||||
write(ConstBufferSequence const& buffers);
|
|
||||||
```]
|
|
||||||
[```
|
|
||||||
lib::error_code send(std::string const & payload,
|
|
||||||
frame::opcode::value op = frame::opcode::text);
|
|
||||||
...
|
|
||||||
lib::error_code send(message_ptr msg);
|
|
||||||
```]
|
|
||||||
]]]]
|
|
||||||
|
|
||||||
[[9. Streaming Messages][
|
|
||||||
|
|
||||||
websocketpp requires that the entire message fit into memory, and that
|
|
||||||
the size is known ahead of time.
|
|
||||||
|
|
||||||
Beast allows callers to compose messages in individual frames. This is
|
|
||||||
useful when the size of the data is not known ahead of time or if it
|
|
||||||
is not desired to buffer the entire message in memory at once before
|
|
||||||
sending it. For example, sending periodic output of a database query
|
|
||||||
running on a coroutine. Or sending the contents of a file in pieces,
|
|
||||||
without bringing it all into memory.
|
|
||||||
|
|
||||||
[table
|
|
||||||
[
|
|
||||||
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L1151 Beast]]
|
|
||||||
[websocketpp]
|
|
||||||
][
|
|
||||||
[```
|
|
||||||
template<class ConstBufferSequence>
|
|
||||||
void
|
|
||||||
write_frame(bool fin,
|
|
||||||
ConstBufferSequence const& buffers);
|
|
||||||
```]
|
|
||||||
[
|
|
||||||
/<not available>/
|
|
||||||
]
|
|
||||||
]]]]
|
|
||||||
|
|
||||||
[[10. Flow Control][
|
|
||||||
|
|
||||||
The websocketpp read implementation continuously reads asynchronously
|
|
||||||
from the network and buffers message data. To prevent unbounded growth
|
|
||||||
and leverage TCP/IP's flow control mechanism, callers can periodically
|
|
||||||
turn this 'read pump' off and back on.
|
|
||||||
|
|
||||||
In contrast a `beast::websocket::stream` does not independently begin
|
|
||||||
background activity, nor does it buffer messages. It receives data only
|
|
||||||
when there is a call to an asynchronous initiation function (for
|
|
||||||
example `beast::websocket::stream::async_read`) with an associated handler.
|
|
||||||
Applications do not need to implement explicit logic to regulate the
|
|
||||||
flow of data. Instead, they follow the traditional model of issuing a
|
|
||||||
read, receiving a read completion, processing the message, then
|
|
||||||
issuing a new read and repeating the process.
|
|
||||||
|
|
||||||
[table
|
|
||||||
[
|
|
||||||
[Beast]
|
|
||||||
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L728 websocketpp]]
|
|
||||||
][
|
|
||||||
[
|
|
||||||
/<implicit>/
|
|
||||||
]
|
|
||||||
[```
|
|
||||||
lib::error_code pause_reading();
|
|
||||||
lib::error_code resume_reading();
|
|
||||||
```]
|
|
||||||
]]]]
|
|
||||||
|
|
||||||
[[11. Connection Establishment][
|
|
||||||
|
|
||||||
websocketpp offers the `endpoint` class which can handle binding and
|
|
||||||
listening to a port, and spawning connection objects.
|
|
||||||
|
|
||||||
Beast does not reinvent the wheel here, callers use the interfaces
|
|
||||||
already in `boost::asio` for receiving incoming connections resolving
|
|
||||||
host names, or establishing outgoing connections. After the socket (or
|
|
||||||
`boost::asio::ssl::stream`) is connected, the `beast::websocket::stream`
|
|
||||||
is constructed around it and the WebSocket handshake can be performed.
|
|
||||||
|
|
||||||
Beast users are free to implement their own "connection manager", but
|
|
||||||
there is no requirement to do so.
|
|
||||||
|
|
||||||
[table
|
|
||||||
[
|
|
||||||
[[@http://www.boost.org/doc/html/boost_asio/reference/async_connect.html Beast],
|
|
||||||
[@http://www.boost.org/doc/html/boost_asio/reference/basic_socket_acceptor/async_accept.html also]]
|
|
||||||
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/asio/endpoint.hpp#L52 websocketpp]]
|
|
||||||
][
|
|
||||||
[```
|
|
||||||
#include <boost/asio.hpp>
|
|
||||||
```]
|
|
||||||
[```
|
|
||||||
template <typename config>
|
|
||||||
class endpoint : public config::socket_type;
|
|
||||||
```]
|
|
||||||
]]]]
|
|
||||||
|
|
||||||
[[12. WebSocket Handshaking][
|
|
||||||
|
|
||||||
Callers invoke `beast::websocket::accept` to perform the WebSocket
|
|
||||||
handshake, but there is no requirement to use this function. Advanced
|
|
||||||
users can perform the WebSocket handshake themselves. Beast WebSocket
|
|
||||||
provides the tools for composing the request or response, and the
|
|
||||||
Beast HTTP interface provides the container and algorithms for sending
|
|
||||||
and receiving HTTP/1 messages including the necessary HTTP Upgrade
|
|
||||||
request for establishing the WebSocket session.
|
|
||||||
|
|
||||||
Beast allows the caller to pass the incoming HTTP Upgrade request for
|
|
||||||
the cases where the caller has already received an HTTP message.
|
|
||||||
This flexibility permits novel and robust implementations. For example,
|
|
||||||
a listening socket that can handshake in multiple protocols on the
|
|
||||||
same port.
|
|
||||||
|
|
||||||
Sometimes callers want to read some bytes on the socket before reading
|
|
||||||
the WebSocket HTTP Upgrade request. Beast allows these already-received
|
|
||||||
bytes to be supplied to an overload of the accepting function to permit
|
|
||||||
sophisticated features. For example, a listening socket that can
|
|
||||||
accept both regular WebSocket and Secure WebSocket (SSL) connections.
|
|
||||||
|
|
||||||
[table
|
|
||||||
[
|
|
||||||
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L501 Beast],
|
|
||||||
[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L401 also]]
|
|
||||||
[websocketpp]
|
|
||||||
][
|
|
||||||
[```
|
|
||||||
template<class ConstBufferSequence>
|
|
||||||
void
|
|
||||||
accept(ConstBufferSequence const& buffers);
|
|
||||||
|
|
||||||
template<class Body, class Headers>
|
|
||||||
void
|
|
||||||
accept(http::request_v1<Body, Headers> const& request);
|
|
||||||
```]
|
|
||||||
[
|
|
||||||
/<not available>/
|
|
||||||
]
|
|
||||||
]]]]
|
|
||||||
|
|
||||||
]
|
|
||||||
]]
|
|
||||||
|
|
||||||
]
|
|
||||||
|
|
||||||
[endsect]
|
|
||||||
|
|
||||||
[endsect]
|
[endsect]
|
||||||
|
454
doc/design/http_comparison.qbk
Normal file
454
doc/design/http_comparison.qbk
Normal file
@ -0,0 +1,454 @@
|
|||||||
|
[/
|
||||||
|
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_comparison HTTP Comparison to Other Libraries]
|
||||||
|
|
||||||
|
There are a few C++ published libraries which implement some of the HTTP
|
||||||
|
protocol. We analyze the message model chosen by those libraries and discuss
|
||||||
|
the advantages and disadvantages relative to Beast.
|
||||||
|
|
||||||
|
The general strategy used by the author to evaluate external libraries is
|
||||||
|
as follows:
|
||||||
|
|
||||||
|
* Review the message model. Can it represent a complete request or
|
||||||
|
response? What level of allocator support is present? How much
|
||||||
|
customization is possible?
|
||||||
|
|
||||||
|
* Review the stream abstraction. This is the type of object, such as
|
||||||
|
a socket, which may be used to parse or serialize (i.e. read and write).
|
||||||
|
Can user defined types be specified? What's the level of conformance to
|
||||||
|
to Asio or Networking-TS concepts?
|
||||||
|
|
||||||
|
* Check treatment of buffers. Does the library manage the buffers or
|
||||||
|
or can users provide their own buffers?
|
||||||
|
|
||||||
|
* How does the library handle corner cases such as trailers,
|
||||||
|
Expect: 100-continue, or deferred commitment of the body type?
|
||||||
|
|
||||||
|
[note
|
||||||
|
Declarations examples from external libraries have been edited:
|
||||||
|
portions have been removed for simplification.
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[heading cpp-netlib]
|
||||||
|
|
||||||
|
[@https://github.com/cpp-netlib/cpp-netlib/tree/092cd570fb179d029d1865aade9f25aae90d97b9 [*cpp-netlib]]
|
||||||
|
is a network programming library previously intended for Boost but not
|
||||||
|
having gone through formal review. As of this writing it still uses the
|
||||||
|
Boost name, namespace, and directory structure although the project states
|
||||||
|
that Boost acceptance is no longer a goal. The library is based on Boost.Asio
|
||||||
|
and bills itself as ['"a collection of network related routines/implementations
|
||||||
|
geared towards providing a robust cross-platform networking library"]. It
|
||||||
|
cites ['"Common Message Type"] as a feature. As of the branch previous
|
||||||
|
linked, it uses these declarations:
|
||||||
|
```
|
||||||
|
template <class Tag>
|
||||||
|
struct basic_message {
|
||||||
|
public:
|
||||||
|
typedef Tag tag;
|
||||||
|
|
||||||
|
typedef typename headers_container<Tag>::type headers_container_type;
|
||||||
|
typedef typename headers_container_type::value_type header_type;
|
||||||
|
typedef typename string<Tag>::type string_type;
|
||||||
|
|
||||||
|
headers_container_type& headers() { return headers_; }
|
||||||
|
headers_container_type const& headers() const { return headers_; }
|
||||||
|
|
||||||
|
string_type& body() { return body_; }
|
||||||
|
string_type const& body() const { return body_; }
|
||||||
|
|
||||||
|
string_type& source() { return source_; }
|
||||||
|
string_type const& source() const { return source_; }
|
||||||
|
|
||||||
|
string_type& destination() { return destination_; }
|
||||||
|
string_type const& destination() const { return destination_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend struct detail::directive_base<Tag>;
|
||||||
|
friend struct detail::wrapper_base<Tag, basic_message<Tag> >;
|
||||||
|
|
||||||
|
mutable headers_container_type headers_;
|
||||||
|
mutable string_type body_;
|
||||||
|
mutable string_type source_;
|
||||||
|
mutable string_type destination_;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
This container is the base class template used to represent HTTP messages.
|
||||||
|
It uses a "tag" type style specializations for a variety of trait classes,
|
||||||
|
allowing for customization of the various parts of the message. For example,
|
||||||
|
a user specializes `headers_container<T>` to determine what container type
|
||||||
|
holds the header fields. We note some problems with the container declaration:
|
||||||
|
|
||||||
|
* The header and body containers may only be default-constructed.
|
||||||
|
|
||||||
|
* No stateful allocator support.
|
||||||
|
|
||||||
|
* There is no way to defer the commitment of the type for `body_` to
|
||||||
|
after the headers are read in.
|
||||||
|
|
||||||
|
* The message model includes a "source" and "destination." This is
|
||||||
|
extraneous metadata associated with the connection which is not part
|
||||||
|
of the HTTP protocol specification and belongs elsewhere.
|
||||||
|
|
||||||
|
* The use of `string_type` (a customization point) for source,
|
||||||
|
destination, and body suggests that `string_type` models a
|
||||||
|
[*ForwardRange] whose `value_type` is `char`. This representation
|
||||||
|
is less than ideal, considering that the library is built on
|
||||||
|
Boost.Asio. Adapting a __DynamicBuffer__ to the required forward
|
||||||
|
range destroys information conveyed by the __ConstBufferSequence__
|
||||||
|
and __MutableBufferSequence__ used in dynamic buffers. The consequence
|
||||||
|
is that cpp-netlib implementations will be less efficient than an
|
||||||
|
equivalent __N4588__ conforming implementation.
|
||||||
|
|
||||||
|
* The library uses specializatons of `string<Tag>` to change the type
|
||||||
|
of string used everywhere, including the body, field name and value
|
||||||
|
pairs, and extraneous metadata such as source and destination. The
|
||||||
|
user may only choose a single type: field name, field values, and
|
||||||
|
the body container will all use the same string type. This limits
|
||||||
|
utility of the customization point. The library's use of the string
|
||||||
|
trait is limited to selecting between `std::string` and `std::wstring`.
|
||||||
|
We do not find this use-case compelling given the limitations.
|
||||||
|
|
||||||
|
* The specialized trait classes generate a proliferation of small
|
||||||
|
additional framework types. To specialize traits, users need to exit
|
||||||
|
their namespace and intrude into the `boost::network::http` namespace.
|
||||||
|
The way the traits are used in the library limits the usefulness
|
||||||
|
of the traits to trivial purpose.
|
||||||
|
|
||||||
|
* The `string<Tag> customization point constrains user defined body types
|
||||||
|
to few possible strategies. There is no way to represent an HTTP message
|
||||||
|
body as a filename with accompanying algorithms to store or retrieve data
|
||||||
|
from the file system.
|
||||||
|
|
||||||
|
The design of the message container in this library is cumbersome
|
||||||
|
with its system of customization using trait specializations. The
|
||||||
|
use of these customizations is extremely limited due to the way they
|
||||||
|
are used in the container declaration, making the design overly
|
||||||
|
complex without corresponding benefit.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[heading Boost.HTTP]
|
||||||
|
|
||||||
|
[@https://github.com/BoostGSoC14/boost.http/tree/45fc1aa828a9e3810b8d87e669b7f60ec100bff4 [*boost.http]]
|
||||||
|
is a library resulting from the 2014 Google Summer of Code. It was submitted
|
||||||
|
for a Boost formal review and rejected in 2015. It is based on Boost.Asio,
|
||||||
|
and development on the library has continued to the present. As of the branch
|
||||||
|
previously linked, it uses these message declarations:
|
||||||
|
```
|
||||||
|
template<class Headers, class Body>
|
||||||
|
struct basic_message
|
||||||
|
{
|
||||||
|
typedef Headers headers_type;
|
||||||
|
typedef Body body_type;
|
||||||
|
|
||||||
|
headers_type &headers();
|
||||||
|
|
||||||
|
const headers_type &headers() const;
|
||||||
|
|
||||||
|
body_type &body();
|
||||||
|
|
||||||
|
const body_type &body() const;
|
||||||
|
|
||||||
|
headers_type &trailers();
|
||||||
|
|
||||||
|
const headers_type &trailers() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
headers_type headers_;
|
||||||
|
body_type body_;
|
||||||
|
headers_type trailers_;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef basic_message<boost::http::headers, std::vector<std::uint8_t>> message;
|
||||||
|
|
||||||
|
template<class Headers, class Body>
|
||||||
|
struct is_message<basic_message<Headers, Body>>: public std::true_type {};
|
||||||
|
```
|
||||||
|
|
||||||
|
* This container cannot model a complete message. The ['start-line] items
|
||||||
|
(method and target for requests, reason-phrase for responses) are
|
||||||
|
communicated out of band, as is the ['http-version]. A function that
|
||||||
|
operates on the message including the start line requires additional
|
||||||
|
parameters. This is evident in one of the
|
||||||
|
[@https://github.com/BoostGSoC14/boost.http/blob/45fc1aa828a9e3810b8d87e669b7f60ec100bff4/example/basic_router.cpp#L81 example programs].
|
||||||
|
The `500` and `"OK"` arguments represent the response ['status-code] and
|
||||||
|
['reason-phrase] respectively:
|
||||||
|
```
|
||||||
|
...
|
||||||
|
http::message reply;
|
||||||
|
...
|
||||||
|
self->socket.async_write_response(500, string_ref("OK"), reply, yield);
|
||||||
|
```
|
||||||
|
|
||||||
|
* `headers_`, `body_`, and `trailers_` may only be default-constructed,
|
||||||
|
since there are no explicitly declared constructors.
|
||||||
|
|
||||||
|
* There is no way to defer the commitment of the [*Body] type to after
|
||||||
|
the headers are read in. This is related to the previous limitation
|
||||||
|
on default-construction.
|
||||||
|
|
||||||
|
* No stateful allocator support. This follows from the previous limitation
|
||||||
|
on default-construction. Buffers for start-line strings must be
|
||||||
|
managed externally from the message object since they are not members.
|
||||||
|
|
||||||
|
* The trailers are stored in a separate object. Aside from the combinatorial
|
||||||
|
explosion of the number of additional constructors necessary to fully
|
||||||
|
support arbitrary forwarded parameter lists for each of the headers, body,
|
||||||
|
and trailers members, the requirement to know in advance whether a
|
||||||
|
particular HTTP field will be located in the headers or the trailers
|
||||||
|
poses an unnecessary complication for general purpose functions that
|
||||||
|
operate on messages.
|
||||||
|
|
||||||
|
* The declarations imply that `std::vector` is a model of [*Body].
|
||||||
|
More formally, that a body is represented by the [*ForwardRange]
|
||||||
|
concept whose `value_type` is an 8-bit integer. This representation
|
||||||
|
is less than ideal, considering that the library is built on
|
||||||
|
Boost.Asio. Adapting a __DynamicBuffer__ to the required forward range
|
||||||
|
destroys information conveyed by the __ConstBufferSequence__ and
|
||||||
|
__MutableBufferSequence__ used in dynamic buffers. The consequence is
|
||||||
|
that Boost.HTTP implementations will be less efficient when dealing
|
||||||
|
with body containers than an equivalent __N4588__ conforming
|
||||||
|
implementation.
|
||||||
|
|
||||||
|
* The [*Body] customization point constrains user defined types to
|
||||||
|
very limited implementation strategies. For example, there is no way
|
||||||
|
to represent an HTTP message body as a filename with accompanying
|
||||||
|
algorithms to store or retrieve data from the file system.
|
||||||
|
|
||||||
|
This representation addresses a narrow range of use cases. It has
|
||||||
|
limited potential for customization and performance. It is more difficult
|
||||||
|
to use because it excludes the start line fields from the model.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[heading C++ REST SDK (cpprestsdk)]
|
||||||
|
|
||||||
|
[@https://github.com/Microsoft/cpprestsdk/tree/381f5aa92d0dfb59e37c0c47b4d3771d8024e09a [*cpprestsdk]]
|
||||||
|
is a Microsoft project which ['"...aims to help C++ developers connect to and
|
||||||
|
interact with services"]. It offers the most functionality of the libraries
|
||||||
|
reviewed here, including support for Websocket services using its websocket++
|
||||||
|
dependency. It can use native APIs such as HTTP.SYS when building Windows
|
||||||
|
based applications, and it can use Boost.Asio. The WebSocket module uses
|
||||||
|
Boost.Asio exclusively.
|
||||||
|
|
||||||
|
As cpprestsdk is developed by a large corporation, it contains quite a bit
|
||||||
|
of functionality and necessarily has more interfaces. We will break down
|
||||||
|
the interfaces used to model messages into more manageable pieces. This
|
||||||
|
is the container used to store the HTTP header fields:
|
||||||
|
```
|
||||||
|
class http_headers
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
...
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<utility::string_t, utility::string_t, _case_insensitive_cmp> m_headers;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
This declaration is quite bare-bones. We note the typical problems of
|
||||||
|
most field containers:
|
||||||
|
|
||||||
|
* The container may only be default-constructed.
|
||||||
|
|
||||||
|
* No support for allocators, stateful or otherwise.
|
||||||
|
|
||||||
|
* There are no customization points at all.
|
||||||
|
|
||||||
|
Now we analyze the structure of
|
||||||
|
the larger message container. The library uses a handle/body idiom. There
|
||||||
|
are two public message container interfaces, one for requests (`http_request`)
|
||||||
|
and one for responses (`http_response`). Each interface maintains a private
|
||||||
|
shared pointer to an implementation class. Public member function calls
|
||||||
|
are routed to the internal implementation. This is the first implementation
|
||||||
|
class, which forms the base class for both the request and response
|
||||||
|
implementations:
|
||||||
|
```
|
||||||
|
namespace details {
|
||||||
|
|
||||||
|
class http_msg_base
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
http_headers &headers() { return m_headers; }
|
||||||
|
|
||||||
|
_ASYNCRTIMP void set_body(const concurrency::streams::istream &instream, const utf8string &contentType);
|
||||||
|
|
||||||
|
/// Set the stream through which the message body could be read
|
||||||
|
void set_instream(const concurrency::streams::istream &instream) { m_inStream = instream; }
|
||||||
|
|
||||||
|
/// Set the stream through which the message body could be written
|
||||||
|
void set_outstream(const concurrency::streams::ostream &outstream, bool is_default) { m_outStream = outstream; m_default_outstream = is_default; }
|
||||||
|
|
||||||
|
const pplx::task_completion_event<utility::size64_t> & _get_data_available() const { return m_data_available; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// Stream to read the message body.
|
||||||
|
concurrency::streams::istream m_inStream;
|
||||||
|
|
||||||
|
/// stream to write the msg body
|
||||||
|
concurrency::streams::ostream m_outStream;
|
||||||
|
|
||||||
|
http_headers m_headers;
|
||||||
|
bool m_default_outstream;
|
||||||
|
|
||||||
|
/// <summary> The TCE is used to signal the availability of the message body. </summary>
|
||||||
|
pplx::task_completion_event<utility::size64_t> m_data_available;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
To understand these declarations we need to first understand that cpprestsdk
|
||||||
|
uses the asynchronous model defined by Microsoft's
|
||||||
|
[@https://msdn.microsoft.com/en-us/library/dd504870.aspx [*Concurrency Runtime]].
|
||||||
|
Identifiers from the [@https://msdn.microsoft.com/en-us/library/jj987780.aspx [*`pplx` namespace]]
|
||||||
|
define common asynchronous patterns such as tasks and events. The
|
||||||
|
`concurrency::streams::istream` parameter and `m_data_available` data member
|
||||||
|
indicates a lack of separation of concerns. The representation of HTTP messages
|
||||||
|
should not be conflated with the asynchronous model used to serialize or
|
||||||
|
parse those messages in the message declarations.
|
||||||
|
|
||||||
|
The next declaration forms the complete implementation class referenced by the
|
||||||
|
handle in the public interface (which follows after):
|
||||||
|
```
|
||||||
|
/// Internal representation of an HTTP request message.
|
||||||
|
class _http_request final : public http::details::http_msg_base, public std::enable_shared_from_this<_http_request>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
_ASYNCRTIMP _http_request(http::method mtd);
|
||||||
|
|
||||||
|
_ASYNCRTIMP _http_request(std::unique_ptr<http::details::_http_server_context> server_context);
|
||||||
|
|
||||||
|
http::method &method() { return m_method; }
|
||||||
|
|
||||||
|
const pplx::cancellation_token &cancellation_token() const { return m_cancellationToken; }
|
||||||
|
|
||||||
|
_ASYNCRTIMP pplx::task<void> reply(const http_response &response);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// Actual initiates sending the response, without checking if a response has already been sent.
|
||||||
|
pplx::task<void> _reply_impl(http_response response);
|
||||||
|
|
||||||
|
http::method m_method;
|
||||||
|
|
||||||
|
std::shared_ptr<progress_handler> m_progress_handler;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace details
|
||||||
|
```
|
||||||
|
|
||||||
|
As before, we note that the implementation class for HTTP requests concerns
|
||||||
|
itself more with the mechanics of sending the message asynchronously than
|
||||||
|
it does with actually modeling the HTTP message as described in __rfc7230__:
|
||||||
|
|
||||||
|
* The constructor accepting `std::unique_ptr<http::details::_http_server_context`
|
||||||
|
breaks encapsulation and separation of concerns. This cannot be extended
|
||||||
|
for user defined server contexts.
|
||||||
|
|
||||||
|
* The "cancellation token" is stored inside the message. This breaks the
|
||||||
|
separation of concerns.
|
||||||
|
|
||||||
|
* The `_reply_impl` function implies that the message implementation also
|
||||||
|
shares responsibility for the means of sending back an HTTP reply. This
|
||||||
|
would be better if it was completely separate from the message container.
|
||||||
|
|
||||||
|
Finally, here is the public class which represents an HTTP request:
|
||||||
|
```
|
||||||
|
class http_request
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
const http::method &method() const { return _m_impl->method(); }
|
||||||
|
|
||||||
|
void set_method(const http::method &method) const { _m_impl->method() = method; }
|
||||||
|
|
||||||
|
/// Extract the body of the request message as a string value, checking that the content type is a MIME text type.
|
||||||
|
/// A body can only be extracted once because in some cases an optimization is made where the data is 'moved' out.
|
||||||
|
pplx::task<utility::string_t> extract_string(bool ignore_content_type = false)
|
||||||
|
{
|
||||||
|
auto impl = _m_impl;
|
||||||
|
return pplx::create_task(_m_impl->_get_data_available()).then([impl, ignore_content_type](utility::size64_t) { return impl->extract_string(ignore_content_type); });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts the body of the request message into a json value, checking that the content type is application/json.
|
||||||
|
/// A body can only be extracted once because in some cases an optimization is made where the data is 'moved' out.
|
||||||
|
pplx::task<json::value> extract_json(bool ignore_content_type = false) const
|
||||||
|
{
|
||||||
|
auto impl = _m_impl;
|
||||||
|
return pplx::create_task(_m_impl->_get_data_available()).then([impl, ignore_content_type](utility::size64_t) { return impl->_extract_json(ignore_content_type); });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the body of the message to the contents of a byte vector. If the 'Content-Type'
|
||||||
|
void set_body(const std::vector<unsigned char> &body_data);
|
||||||
|
|
||||||
|
/// Defines a stream that will be relied on to provide the body of the HTTP message when it is
|
||||||
|
/// sent.
|
||||||
|
void set_body(const concurrency::streams::istream &stream, const utility::string_t &content_type = _XPLATSTR("application/octet-stream"));
|
||||||
|
|
||||||
|
/// Defines a stream that will be relied on to hold the body of the HTTP response message that
|
||||||
|
/// results from the request.
|
||||||
|
void set_response_stream(const concurrency::streams::ostream &stream);
|
||||||
|
{
|
||||||
|
return _m_impl->set_response_stream(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines a callback function that will be invoked for every chunk of data uploaded or downloaded
|
||||||
|
/// as part of the request.
|
||||||
|
void set_progress_handler(const progress_handler &handler);
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class http::details::_http_request;
|
||||||
|
friend class http::client::http_client;
|
||||||
|
|
||||||
|
std::shared_ptr<http::details::_http_request> _m_impl;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
It is clear from this declaration that the goal of the message model in
|
||||||
|
this library is driven by its use-case (interacting with REST servers)
|
||||||
|
and not to model HTTP messages generally. We note problems similar to
|
||||||
|
the other declarations:
|
||||||
|
|
||||||
|
* There are no compile-time customization points at all. The only
|
||||||
|
customization is in the `concurrency::streams::istream` and
|
||||||
|
`concurrency::streams::ostream` reference parameters. Presumably,
|
||||||
|
these are abstract interfaces which may be subclassed by users
|
||||||
|
to achieve custom behaviors.
|
||||||
|
|
||||||
|
* The extraction of the body is conflated with the asynchronous model.
|
||||||
|
|
||||||
|
* No way to define an allocator for the container used when extracting
|
||||||
|
the body.
|
||||||
|
|
||||||
|
* A body can only be extracted once, limiting the use of this container
|
||||||
|
when using a functional programming style.
|
||||||
|
|
||||||
|
* Setting the body requires either a vector or a `concurrency::streams::istream`.
|
||||||
|
No user defined types are possible.
|
||||||
|
|
||||||
|
* The HTTP request container conflates HTTP response behavior (see the
|
||||||
|
`set_response_stream` member). Again this is likely purpose-driven but
|
||||||
|
the lack of separation of concerns limits this library to only the
|
||||||
|
uses explicitly envisioned by the authors.
|
||||||
|
|
||||||
|
The general theme of the HTTP message model in cpprestsdk is "no user
|
||||||
|
definable customizations". There is no allocator support, and no
|
||||||
|
separation of concerns. It is designed to perform a specific set of
|
||||||
|
behaviors. In other words, it does not follow the open/closed principle.
|
||||||
|
|
||||||
|
Tasks in the Concurrency Runtime operate in a fashion similar to
|
||||||
|
`std::future`, but with some improvements such as continuations which
|
||||||
|
are not yet in the C++ standard. The costs of using a task based
|
||||||
|
asynchronous interface instead of completion handlers is well
|
||||||
|
documented: synchronization points along the call chain of composed
|
||||||
|
task operations which cannot be optimized away. See:
|
||||||
|
[@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3747.pdf
|
||||||
|
[*A Universal Model for Asynchronous Operations]] (Kohlhoff).
|
||||||
|
|
||||||
|
[endsect]
|
284
doc/design/http_message.qbk
Normal file
284
doc/design/http_message.qbk
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
[/
|
||||||
|
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_message HTTP Message Container]
|
||||||
|
|
||||||
|
In this section we describe the problem of modeling HTTP messages and explain
|
||||||
|
how the library arrived at its solution, with a discussion of the benefits
|
||||||
|
and drawbacks of the design choices. The goal for creating a message model
|
||||||
|
is to create a container with value semantics, possibly movable and/or
|
||||||
|
copyable, that completely describes the message. More formally, the container
|
||||||
|
has all of the information necessary for the serialization algorithms to
|
||||||
|
represent the entire message as a series of octets, and that the parsing
|
||||||
|
algorithms store all of the captured information about an entire message
|
||||||
|
in the container.
|
||||||
|
|
||||||
|
In addition to capturing the entirety of a message, we would like the message
|
||||||
|
container to contain enough customization points to permit the following:
|
||||||
|
allocator support, user-defined containers to represent header fields, and
|
||||||
|
user-defined containers to represent the body. And finally, because requests
|
||||||
|
and responses have slightly different information in the ['start-line], we
|
||||||
|
would like the containers for requests and responses to be represented by
|
||||||
|
different types.
|
||||||
|
|
||||||
|
Here is our first attempt at declaring some message containers:
|
||||||
|
|
||||||
|
[table
|
||||||
|
[[
|
||||||
|
```
|
||||||
|
/// An HTTP request
|
||||||
|
template<class Fields, class Body>
|
||||||
|
struct request
|
||||||
|
{
|
||||||
|
int version;
|
||||||
|
std::string method;
|
||||||
|
std::string target;
|
||||||
|
Fields fields;
|
||||||
|
typename Body::value_type body;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
][
|
||||||
|
```
|
||||||
|
/// An HTTP response
|
||||||
|
template<class Fields, class Body>
|
||||||
|
struct response
|
||||||
|
{
|
||||||
|
int version;
|
||||||
|
int status;
|
||||||
|
std::string reason;
|
||||||
|
Fields fields;
|
||||||
|
typename Body::value_type body;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
]]
|
||||||
|
]
|
||||||
|
|
||||||
|
Here we have accomplished a few things. Request and response objects are
|
||||||
|
different types. The user can choose the container used to represent the
|
||||||
|
fields. And the user can choose the [*Body] type, which is a concept
|
||||||
|
defining not only the type of `body` member but also the algorithms used
|
||||||
|
to transfer information in and out of that member when performing
|
||||||
|
serialization and parsing.
|
||||||
|
|
||||||
|
However, a problem arises. How do we write a function which can accept
|
||||||
|
an object that is either a request or a response? As written, the only
|
||||||
|
ovious solution is to make the message a template type. Additional traits
|
||||||
|
classes would then be needed to make sure that the passed object has a
|
||||||
|
valid type which meets the requirements. We can avoid those complexities
|
||||||
|
by renaming the containers and making them partial specializations of a
|
||||||
|
single class, like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
/// An HTTP message
|
||||||
|
template<bool isRequest, class Fields, class Body>
|
||||||
|
struct message;
|
||||||
|
|
||||||
|
/// An HTTP request
|
||||||
|
template<class Fields, class Body>
|
||||||
|
struct message<true, Fields, Body>
|
||||||
|
{
|
||||||
|
int version;
|
||||||
|
std::string method;
|
||||||
|
std::string target;
|
||||||
|
Fields fields;
|
||||||
|
typename Body::value_type body;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An HTTP response
|
||||||
|
template<bool isRequest, class Fields, class Body>
|
||||||
|
struct response<false, Fields, Body>
|
||||||
|
{
|
||||||
|
int version;
|
||||||
|
int status;
|
||||||
|
std::string reason;
|
||||||
|
Fields fields;
|
||||||
|
typename Body::value_type body;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we can declare a function which takes any message as a parameter:
|
||||||
|
```
|
||||||
|
template<bool isRequest, class Fields, class Body>
|
||||||
|
void f(message<isRequest, Fields, Body>& msg);
|
||||||
|
```
|
||||||
|
|
||||||
|
This function can manipulate the fields common to requests and responses.
|
||||||
|
If it needs to access the other fields, it can do so using tag dispatch
|
||||||
|
with an object of type `std::integral_constant<bool, isRequest>`.
|
||||||
|
|
||||||
|
Often, in non-trivial HTTP applications, we want to read the HTTP header
|
||||||
|
and examine its contents before choosing a top for [*Body]. To accomplish
|
||||||
|
this, there needs to be a way to model the header portion of a message.
|
||||||
|
And we'd like to do this in a way that allows functions which take the
|
||||||
|
header as a parameter, to also accept a type representing the whole
|
||||||
|
message (the function will see just the header part). This suggests
|
||||||
|
inheritance:
|
||||||
|
```
|
||||||
|
/// An HTTP message header
|
||||||
|
template<bool isRequest, class Fields>
|
||||||
|
struct header;
|
||||||
|
|
||||||
|
/// An HTTP request header
|
||||||
|
template<class Fields>
|
||||||
|
struct header<true, Fields>
|
||||||
|
{
|
||||||
|
int version;
|
||||||
|
std::string method;
|
||||||
|
std::string target;
|
||||||
|
Fields fields;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An HTTP response header
|
||||||
|
template<class Fields>
|
||||||
|
struct header<false, Fields>
|
||||||
|
{
|
||||||
|
int version;
|
||||||
|
int status;
|
||||||
|
std::string reason;
|
||||||
|
Fields fields;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An HTTP message
|
||||||
|
template<bool isRequest, class Fields, class Body>
|
||||||
|
struct message : header<isRequest, Fields>
|
||||||
|
{
|
||||||
|
typename Body::value_type body;
|
||||||
|
|
||||||
|
/// Construct from a `header`
|
||||||
|
message(header<isRequest, Fields>&& h);
|
||||||
|
};
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the `message` class now has a constructor allowing messages
|
||||||
|
to be constructed from a similarly typed `header`. This handles the case
|
||||||
|
where the user already has the header and wants to make a commitment to the
|
||||||
|
type for [*Body]. This also lets us declare a function accepting any header:
|
||||||
|
```
|
||||||
|
template<bool isRequest, class Fields>
|
||||||
|
void f(header<isRequest, Fields>& msg);
|
||||||
|
```
|
||||||
|
|
||||||
|
Until now we have not given significant consideration to the constructors
|
||||||
|
of the `message` class. But to achieve all our goals we will need to make
|
||||||
|
sure that there are enough constructor overloads to not only provide for
|
||||||
|
the special copy and move members if the instantiated types support it,
|
||||||
|
but also allow the fields container and body container to be constructed
|
||||||
|
with arbitrary variadic lists of parameters. This allows those members
|
||||||
|
to properly support allocators.
|
||||||
|
|
||||||
|
The solution used in the library is to treat the message like a `std::pair`
|
||||||
|
for the purposes of construction, except that instead of `first` and `last`
|
||||||
|
we have `fields` and `body`. This means that single-argument constructors
|
||||||
|
for those fields should be accessible as they are with `std::pair`, and
|
||||||
|
that a mechanism identical to the pair's use of `std::piecewise_construct`
|
||||||
|
should be provided. Those constructors are too complex to repeat here, but
|
||||||
|
interested readers can view the declarations in the corresponding header
|
||||||
|
file.
|
||||||
|
|
||||||
|
There is now significant progress with our message container but a stumbling
|
||||||
|
block remains. The presence of `std::string` members is an obstacle to our
|
||||||
|
goal of making messages allocator-aware. One obvious solution is to add an
|
||||||
|
allocator to the template parameter list of the header and message classes,
|
||||||
|
and allow the string based members to construct with instances of those
|
||||||
|
allocators. This is unsatisfying because of the combinatorial explosion of
|
||||||
|
constructor variations needed to support the scheme. It also means that
|
||||||
|
request messages could have [*four] different allocators: two for the fields
|
||||||
|
and body, and two for the method and target strings. A better solution is
|
||||||
|
needed.
|
||||||
|
|
||||||
|
To make allocators workable, we make a simple change to the interface and
|
||||||
|
then engineer a clever concession. First, the interface change:
|
||||||
|
```
|
||||||
|
/// An HTTP request header
|
||||||
|
template<class Fields>
|
||||||
|
struct header<true, Fields>
|
||||||
|
{
|
||||||
|
int version;
|
||||||
|
string_view method() const;
|
||||||
|
void method(string_view);
|
||||||
|
string_view target(); const;
|
||||||
|
void target(string_view);
|
||||||
|
Fields fields;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An HTTP response header
|
||||||
|
template<class Fields>
|
||||||
|
struct header<false, Fields>
|
||||||
|
{
|
||||||
|
int version;
|
||||||
|
int status;
|
||||||
|
string_view reason() const;
|
||||||
|
void reason(string_view);
|
||||||
|
Fields fields;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
This approach replaces public data members with traditional accessors
|
||||||
|
using non-owning references to string buffers. Now we make a concession:
|
||||||
|
management of the corresponding string is delegated to the [*Fields]
|
||||||
|
container, which can already be allocator aware and constructed with the
|
||||||
|
necessary allocator parameter via the provided constructor overloads for
|
||||||
|
`message`. The delegation implementation looks like this (only the
|
||||||
|
response header specialization is shown):
|
||||||
|
```
|
||||||
|
/// An HTTP response header
|
||||||
|
template<class Fields>
|
||||||
|
struct header<false, Fields>
|
||||||
|
{
|
||||||
|
int version;
|
||||||
|
int status;
|
||||||
|
|
||||||
|
auto
|
||||||
|
reason() const -> decltype(std::declval<Fields>().reason()) const
|
||||||
|
{
|
||||||
|
return fields.reason();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Value>
|
||||||
|
void
|
||||||
|
reason(Value&& value)
|
||||||
|
{
|
||||||
|
fields.reason(std::forward<Value>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
Fields fields;
|
||||||
|
```
|
||||||
|
|
||||||
|
An advantage of this technique is that user-provided implementations of
|
||||||
|
[*Fields] may use arbitrary representation strategies. For example, instead
|
||||||
|
of explicitly storing the method as a string, store it as an enumerated
|
||||||
|
integer with a lookup table of static strings for known message types.
|
||||||
|
|
||||||
|
Now that we've accomplished our initial goals and more, there is one small
|
||||||
|
quality of life improvement to make. Users will choose different types for
|
||||||
|
`Body` far more often than they will for `Fields`. Thus, we swap the order
|
||||||
|
of these types and provide a default:
|
||||||
|
```
|
||||||
|
/// An HTTP header
|
||||||
|
template<bool isRequest, class Body, class Fields = fields>
|
||||||
|
struct header;
|
||||||
|
|
||||||
|
/// An HTTP message
|
||||||
|
template<bool isRequest, class Body, class Fields = fields>
|
||||||
|
struct message;
|
||||||
|
```
|
||||||
|
|
||||||
|
This container is also capable of representing complete HTTP/2 messages.
|
||||||
|
Not because it was explicitly designed for, but because the IETF wanted to
|
||||||
|
preserve message compatibility with HTTP/1. Aside from version specific
|
||||||
|
fields such as Connection, the contents of HTTP/1 and HTTP/2 messages are
|
||||||
|
identical even though their serialized representation is considerably
|
||||||
|
different. The message model presented in this library is ready for HTTP/2.
|
||||||
|
|
||||||
|
In conclusion, this representation for the message container is well thought
|
||||||
|
out, provides comprehensive flexibility, and avoids the necessity of defining
|
||||||
|
additional traits classes. User declarations of functions that accept headers
|
||||||
|
or messages as parameters are easy to write in a variety of ways to accomplish
|
||||||
|
different results, without forcing cumbersome SFINAE declarations everywhere.
|
||||||
|
|
||||||
|
[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:review For Reviewers]
|
[section:review Boost Formal Review 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
|
||||||
@ -58,6 +58,12 @@ about Beast and other HTTP libraries that have gone through formal review.
|
|||||||
[[
|
[[
|
||||||
"There's no HTTP/2 support yet!"
|
"There's no HTTP/2 support yet!"
|
||||||
][
|
][
|
||||||
|
Many reviewers feel that HTTP/2 support is an essential feature of
|
||||||
|
a HTTP library. The authors agree that HTTP/2 is important but also
|
||||||
|
feel that the most sensible implementation is one that does not re-use
|
||||||
|
the same network reading and writing interface for 2 as that for 1.0
|
||||||
|
and 1.1.
|
||||||
|
|
||||||
The Beast HTTP message model was designed with the new protocol
|
The Beast HTTP message model was designed with the new protocol
|
||||||
in mind and should be evaluated in that context. There are plans
|
in mind and should be evaluated in that context. There are plans
|
||||||
to add HTTP/2 in the future, but there is no rush to do so.
|
to add HTTP/2 in the future, but there is no rush to do so.
|
||||||
@ -66,6 +72,16 @@ about Beast and other HTTP libraries that have gone through formal review.
|
|||||||
It is the author's position that there is sufficient value in
|
It is the author's position that there is sufficient value in
|
||||||
Beast's HTTP/1-only implementation that the lack of HTTP/2
|
Beast's HTTP/1-only implementation that the lack of HTTP/2
|
||||||
should not be a barrier to acceptance.
|
should not be a barrier to acceptance.
|
||||||
|
|
||||||
|
The Beast HTTP message model is suitable for HTTP/2 and can be re-used.
|
||||||
|
The IETF HTTP Working Group adopted message compatiblity with HTTP/1.x
|
||||||
|
as an explicit goal. A parser can simply emit full headers after
|
||||||
|
decoding the compressed HTTP/2 headers. The stream ID is not logically
|
||||||
|
part of the message but rather message metadata and should be
|
||||||
|
communicated out-of-band (see below). HTTP/2 sessions begin with a
|
||||||
|
traditional HTTP/1.1 Upgrade similar in fashion to the WebSocket
|
||||||
|
upgrade. An HTTP/2 implementation can use existing Beast.HTTP primitives
|
||||||
|
to perform this handshake.
|
||||||
]]
|
]]
|
||||||
[[
|
[[
|
||||||
"This should work with standalone-Asio!"
|
"This should work with standalone-Asio!"
|
||||||
@ -106,7 +122,7 @@ about Beast and other HTTP libraries that have gone through formal review.
|
|||||||
is lifted from another extremely popular project which has performance
|
is lifted from another extremely popular project which has performance
|
||||||
as a design goal (see https://github.com/h2o/picohttpparser).
|
as a design goal (see https://github.com/h2o/picohttpparser).
|
||||||
|
|
||||||
From: http://www.boost.org/development/requirements.html
|
From: [@http://www.boost.org/development/requirements.html]
|
||||||
|
|
||||||
"Aim first for clarity and correctness; optimization should
|
"Aim first for clarity and correctness; optimization should
|
||||||
be only a secondary concern in most Boost libraries."
|
be only a secondary concern in most Boost libraries."
|
||||||
@ -143,6 +159,121 @@ about Beast and other HTTP libraries that have gone through formal review.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[
|
||||||
|
"Some more advanced examples, e.g. including TLS with client/server
|
||||||
|
certificates would help."
|
||||||
|
][
|
||||||
|
The HTTP interface doesn't try to reinvent the wheel, it just uses
|
||||||
|
the `boost::asio::ip::tcp::socket` or `boost::asio::ssl::stream` that
|
||||||
|
you set up beforehand. Callers use the interfaces already existing
|
||||||
|
on those objects to make outgoing connections, accept incoming connections,
|
||||||
|
or establish TLS sessions with certificates. We find the available Asio
|
||||||
|
examples for performing these tasks sufficient.
|
||||||
|
]]
|
||||||
|
|
||||||
|
[[
|
||||||
|
"A built-in HTTP router?"
|
||||||
|
][
|
||||||
|
We presume this means a facility to match expressions against the URI
|
||||||
|
in HTTP requests, and dispatch them to calling code. The authors feel
|
||||||
|
that this is a responsibility of higher level code. Beast does
|
||||||
|
not try to offer a web server.
|
||||||
|
]]
|
||||||
|
|
||||||
|
[[
|
||||||
|
"HTTP Cookies? Forms/File Uploads?"
|
||||||
|
][
|
||||||
|
Cookies, or managing these types of HTTP headers in general, is the
|
||||||
|
responsibility of higher levels. Beast.HTTP just tries to get complete
|
||||||
|
messages to and from the calling code. It deals in the HTTP headers just
|
||||||
|
enough to process the message body and leaves the rest to callers. However,
|
||||||
|
for forms and file uploads the symmetric interface of the message class
|
||||||
|
allows HTTP requests to include arbitrary body types including those needed
|
||||||
|
to upload a file or fill out a form.
|
||||||
|
]]
|
||||||
|
|
||||||
|
[[
|
||||||
|
"...supporting TLS (is this a feature? If not this would be a show-stopper),
|
||||||
|
etc."
|
||||||
|
][
|
||||||
|
Beast does not provide direct facilities for implementing TLS connections;
|
||||||
|
however, the interfaces already existing on the `boost::asio::ssl::stream`
|
||||||
|
are available and can be used to establish secure connections. Then,
|
||||||
|
functions like `http::read` or `http::async_write` can work with those
|
||||||
|
encrypted connections with no problem.
|
||||||
|
]]
|
||||||
|
|
||||||
|
[[
|
||||||
|
"There should also be more examples of how to integrate the http service
|
||||||
|
with getting files from the file system, generating responses CGI-style"
|
||||||
|
][
|
||||||
|
The design goal for the library is to not try to invent a web server.
|
||||||
|
We feel that there is a strong need for a basic implementation that
|
||||||
|
models the HTTP message and provides functions to send and receive them
|
||||||
|
over Asio. Such an implementation should serve as a building block upon
|
||||||
|
which higher abstractions such as the aforementioned HTTP service or
|
||||||
|
cgi-gateway can be built.
|
||||||
|
|
||||||
|
One of the example programs implements a simple HTTP server that
|
||||||
|
delivers files from the filesystem.
|
||||||
|
]]
|
||||||
|
|
||||||
|
[[
|
||||||
|
"You should send a 100-continue to ask for the rest of the body if required."
|
||||||
|
][
|
||||||
|
The Beast interface supporst this functionality (by allowing this
|
||||||
|
special case of "split" message parsing and serialization). Specifically,
|
||||||
|
it lets callers read the request up to just before the body,
|
||||||
|
and let callers write the request up to just before the body. However,
|
||||||
|
making use of this behavior is up to callers (since Beast is low level).
|
||||||
|
]]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[
|
||||||
|
"I would also like to see instances of this library being used
|
||||||
|
in production. That would give some evidence that the design
|
||||||
|
works in practice."
|
||||||
|
][
|
||||||
|
Beast.HTTP and Beast.WebSocket are production ready and currently
|
||||||
|
running on public servers receiving traffic and handling millions of
|
||||||
|
dollars worth of financial transactions daily. The servers run [*rippled],
|
||||||
|
open source software ([@https://github.com/ripple/rippled repository])
|
||||||
|
implementing the
|
||||||
|
[@https://ripple.com/files/ripple_consensus_whitepaper.pdf [*Ripple Consensus Protocol]],
|
||||||
|
technology provided by [@http://ripple.com Ripple].
|
||||||
|
]]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[
|
||||||
|
What about WebSocket message compression?
|
||||||
|
][
|
||||||
|
Beast WebSocket supports the permessage-deflate extension described in
|
||||||
|
[@https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-00 draft-ietf-hybi-permessage-compression-00].
|
||||||
|
The library comes with a header-only, C++11 port of ZLib's "deflate" codec
|
||||||
|
used in the implementation of the permessage-deflate extension.
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
Where is the WebSocket TLS/SSL interface?
|
||||||
|
][
|
||||||
|
The `websocket::stream` wraps the socket or stream that you provide
|
||||||
|
(for example, a `boost::asio::ip::tcp::socket` or a
|
||||||
|
`boost::asio::ssl::stream`). You establish your TLS connection using the
|
||||||
|
interface on `ssl::stream` like shown in all of the Asio examples, then
|
||||||
|
construct your `websocket::stream` around it. It works perfectly fine;
|
||||||
|
Beast.WebSocket doesn't try to reinvent the wheel or put a fresh coat of
|
||||||
|
interface paint on the `ssl::stream`.
|
||||||
|
|
||||||
|
The WebSocket implementation [*does] provide support for shutting down
|
||||||
|
the TLS connection through the use of the ADL compile-time virtual functions
|
||||||
|
[link beast.ref.websocket__teardown `teardown`] and
|
||||||
|
[link beast.ref.websocket__async_teardown `async_teardown`]. These will
|
||||||
|
properly close the connection as per rfc6455 and overloads are available
|
||||||
|
for TLS streams. Callers may provide their own overloads of these functions
|
||||||
|
for user-defined next layer types.
|
||||||
|
]]
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[endsect]
|
[endsect]
|
431
doc/design/websocket_zaphoyd.qbk
Normal file
431
doc/design/websocket_zaphoyd.qbk
Normal file
@ -0,0 +1,431 @@
|
|||||||
|
[/
|
||||||
|
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:websocket_zaphoyd Comparison to Zaphoyd Studios WebSocket++]
|
||||||
|
|
||||||
|
[variablelist
|
||||||
|
|
||||||
|
[[
|
||||||
|
How does this compare to [@https://www.zaphoyd.com/websocketpp websocketpp],
|
||||||
|
an alternate header-only WebSocket implementation?
|
||||||
|
][
|
||||||
|
[variablelist
|
||||||
|
|
||||||
|
[[1. Synchronous Interface][
|
||||||
|
|
||||||
|
Beast offers full support for WebSockets using a synchronous interface. It
|
||||||
|
uses the same style of interfaces found in Boost.Asio: versions that throw
|
||||||
|
exceptions, or versions that return the error code in a reference parameter:
|
||||||
|
|
||||||
|
[table
|
||||||
|
[
|
||||||
|
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L774 Beast]]
|
||||||
|
[websocketpp]
|
||||||
|
][
|
||||||
|
[```
|
||||||
|
template<class DynamicBuffer>
|
||||||
|
void
|
||||||
|
read(opcode& op, DynamicBuffer& dynabuf)
|
||||||
|
```]
|
||||||
|
[
|
||||||
|
/<not available>/
|
||||||
|
]
|
||||||
|
]]]]
|
||||||
|
|
||||||
|
[[2. Connection Model][
|
||||||
|
|
||||||
|
websocketpp supports multiple transports by utilizing a trait, the `config::transport_type`
|
||||||
|
([@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/asio/connection.hpp#L60 asio transport example])
|
||||||
|
To get an idea of the complexity involved with implementing a transport,
|
||||||
|
compare the asio transport to the
|
||||||
|
[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/iostream/connection.hpp#L59 `iostream` transport]
|
||||||
|
(a layer that allows websocket communication over a `std::iostream`).
|
||||||
|
|
||||||
|
In contrast, Beast abstracts the transport by defining just one [*`NextLayer`]
|
||||||
|
template argument The type requirements for [*`NextLayer`] are
|
||||||
|
already familiar to users as they are documented in Asio:
|
||||||
|
__AsyncReadStream__, __AsyncWriteStream__, __SyncReadStream__, __SyncWriteStream__.
|
||||||
|
|
||||||
|
The type requirements for instantiating `beast::websocket::stream` versus
|
||||||
|
`websocketpp::connection` with user defined types are vastly reduced
|
||||||
|
(18 functions versus 2). Note that websocketpp connections are passed by
|
||||||
|
`shared_ptr`. Beast does not use `shared_ptr` anywhere in its public interface.
|
||||||
|
A `beast::websocket::stream` is constructible and movable in a manner identical
|
||||||
|
to a `boost::asio::ip::tcp::socket`. Callers can put such objects in a
|
||||||
|
`shared_ptr` if they want to, but there is no requirement to do so.
|
||||||
|
|
||||||
|
[table
|
||||||
|
[
|
||||||
|
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp Beast]]
|
||||||
|
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L234 websocketpp]]
|
||||||
|
][
|
||||||
|
[```
|
||||||
|
template<class NextLayer>
|
||||||
|
class stream
|
||||||
|
{
|
||||||
|
NextLayer next_layer_;
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```]
|
||||||
|
[```
|
||||||
|
template <typename config>
|
||||||
|
class connection
|
||||||
|
: public config::transport_type::transport_con_type
|
||||||
|
, public config::connection_base
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef lib::shared_ptr<type> ptr;
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```]
|
||||||
|
]]]]
|
||||||
|
|
||||||
|
[[3. Client and Server Role][
|
||||||
|
|
||||||
|
websocketpp provides multi-role support through a hierarchy of
|
||||||
|
different classes. A `beast::websocket::stream` is role-agnostic, it
|
||||||
|
offers member functions to perform both client and server handshakes
|
||||||
|
in the same class. The same types are used for client and server
|
||||||
|
streams.
|
||||||
|
|
||||||
|
[table
|
||||||
|
[
|
||||||
|
[Beast]
|
||||||
|
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/roles/server_endpoint.hpp#L39 websocketpp],
|
||||||
|
[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/roles/client_endpoint.hpp#L42 also]]
|
||||||
|
][
|
||||||
|
[
|
||||||
|
/<not needed>/
|
||||||
|
]
|
||||||
|
[```
|
||||||
|
template <typename config>
|
||||||
|
class client : public endpoint<connection<config>,config>;
|
||||||
|
template <typename config>
|
||||||
|
class server : public endpoint<connection<config>,config>;
|
||||||
|
```]
|
||||||
|
]]]]
|
||||||
|
|
||||||
|
[[4. Thread Safety][
|
||||||
|
|
||||||
|
websocketpp uses mutexes to protect shared data from concurrent
|
||||||
|
access. In contrast, Beast does not use mutexes anywhere in its
|
||||||
|
implementation. Instead, it follows the Asio pattern. Calls to
|
||||||
|
asynchronous initiation functions use the same method to invoke
|
||||||
|
intermediate handlers as the method used to invoke the final handler,
|
||||||
|
through the __asio_handler_invoke__ mechanism.
|
||||||
|
|
||||||
|
The only requirement in Beast is that calls to asynchronous initiation
|
||||||
|
functions are made from the same implicit or explicit strand. For
|
||||||
|
example, if the `io_service` associated with a `beast::websocket::stream`
|
||||||
|
is single threaded, this counts as an implicit strand and no performance
|
||||||
|
costs associated with mutexes are incurred.
|
||||||
|
|
||||||
|
[table
|
||||||
|
[
|
||||||
|
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/impl/read_frame_op.ipp#L118 Beast]]
|
||||||
|
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/iostream/connection.hpp#L706 websocketpp]]
|
||||||
|
][
|
||||||
|
[```
|
||||||
|
template <class Function>
|
||||||
|
friend
|
||||||
|
void asio_handler_invoke(Function&& f, read_frame_op* op)
|
||||||
|
{
|
||||||
|
return boost_asio_handler_invoke_helpers::invoke(f, op->d_->h);
|
||||||
|
}
|
||||||
|
```]
|
||||||
|
[```
|
||||||
|
mutex_type m_read_mutex;
|
||||||
|
```]
|
||||||
|
]]]]
|
||||||
|
|
||||||
|
[[5. Callback Model][
|
||||||
|
|
||||||
|
websocketpp requires a one-time call to set the handler for each event
|
||||||
|
in its interface (for example, upon message receipt). The handler is
|
||||||
|
represented by a `std::function` equivalent. Its important to recognize
|
||||||
|
that the websocketpp interface performs type-erasure on this handler.
|
||||||
|
|
||||||
|
In comparison, Beast handlers are specified in a manner identical to
|
||||||
|
Boost.Asio. They are function objects which can be copied or moved but
|
||||||
|
most importantly they are not type erased. The compiler can see
|
||||||
|
through the type directly to the implementation, permitting
|
||||||
|
optimization. Furthermore, Beast follows the Asio rules for treatment
|
||||||
|
of handlers. It respects any allocation, continuation, or invocation
|
||||||
|
customizations associated with the handler through the use of argument
|
||||||
|
dependent lookup overloads of functions such as `asio_handler_allocate`.
|
||||||
|
|
||||||
|
The Beast completion handler is provided at the call site. For each
|
||||||
|
call to an asynchronous initiation function, it is guaranteed that
|
||||||
|
there will be exactly one final call to the handler. This functions
|
||||||
|
exactly the same way as the asynchronous initiation functions found in
|
||||||
|
Boost.Asio, allowing the composition of higher level abstractions.
|
||||||
|
|
||||||
|
[table
|
||||||
|
[
|
||||||
|
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L834 Beast]]
|
||||||
|
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L281 websocketpp],
|
||||||
|
[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L473 also]]
|
||||||
|
][
|
||||||
|
[```
|
||||||
|
template<class DynamicBuffer, class ReadHandler>
|
||||||
|
typename async_completion<ReadHandler, void(error_code)>::result_type
|
||||||
|
async_read(opcode& op, DynamicBuffer& dynabuf, ReadHandler&& handler);
|
||||||
|
```]
|
||||||
|
[```
|
||||||
|
typedef lib::function<void(connection_hdl,message_ptr)> message_handler;
|
||||||
|
void set_message_handler(message_handler h);
|
||||||
|
```]
|
||||||
|
]]]]
|
||||||
|
|
||||||
|
[[6. Extensible Asynchronous Model][
|
||||||
|
|
||||||
|
Beast fully supports the
|
||||||
|
[@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3896.pdf Extensible Asynchronous Model]
|
||||||
|
developed by Christopher Kohlhoff, author of Boost.Asio (see Section 8).
|
||||||
|
|
||||||
|
Beast websocket asynchronous interfaces may be used seamlessly with
|
||||||
|
`std::future` stackful/stackless coroutines, or user defined customizations.
|
||||||
|
|
||||||
|
[table
|
||||||
|
[
|
||||||
|
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/impl/stream.ipp#L378 Beast]]
|
||||||
|
[websocketpp]
|
||||||
|
][
|
||||||
|
[```
|
||||||
|
beast::async_completion<ReadHandler, void(error_code)> completion(handler);
|
||||||
|
read_op<DynamicBuffer, decltype(completion.handler)>{
|
||||||
|
completion.handler, *this, op, buffer};
|
||||||
|
return completion.result.get();
|
||||||
|
```]
|
||||||
|
[
|
||||||
|
/<not available>/
|
||||||
|
]
|
||||||
|
]]]]
|
||||||
|
|
||||||
|
[[7. Message Buffering][
|
||||||
|
|
||||||
|
websocketpp defines a message buffer, passed in arguments by
|
||||||
|
`shared_ptr`, and an associated message manager which permits
|
||||||
|
aggregation and reuse of memory. The implementation of
|
||||||
|
`websocketpp::message` uses a `std::string` to hold the payload. If an
|
||||||
|
incoming message is broken up into multiple frames, the string may be
|
||||||
|
reallocated for each continuation frame. The `std::string` always uses
|
||||||
|
the standard allocator, it is not possible to customize the choice of
|
||||||
|
allocator.
|
||||||
|
|
||||||
|
Beast allows callers to specify the object for receiving the message
|
||||||
|
or frame data, which is of any type meeting the requirements of
|
||||||
|
__DynamicBuffer__ (modeled after `boost::asio::streambuf`).
|
||||||
|
|
||||||
|
Beast comes with the class __basic_multi_buffer__, an efficient
|
||||||
|
implementation of the __DynamicBuffer__ concept which makes use of multiple
|
||||||
|
allocated octet arrays. If an incoming message is broken up into
|
||||||
|
multiple pieces, no reallocation occurs. Instead, new allocations are
|
||||||
|
appended to the sequence when existing allocations are filled. Beast
|
||||||
|
does not impose any particular memory management model on callers. The
|
||||||
|
__basic_multi_buffer__ provided by beast supports standard allocators through
|
||||||
|
a template argument. Use the __DynamicBuffer__ that comes with beast,
|
||||||
|
customize the allocator if you desire, or provide your own type that
|
||||||
|
meets the requirements.
|
||||||
|
|
||||||
|
[table
|
||||||
|
[
|
||||||
|
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L774 Beast]]
|
||||||
|
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/message_buffer/message.hpp#L78 websocketpp]]
|
||||||
|
][
|
||||||
|
[```
|
||||||
|
template<class DynamicBuffer>
|
||||||
|
read(opcode& op, DynamicBuffer& dynabuf);
|
||||||
|
```]
|
||||||
|
[```
|
||||||
|
template <template<class> class con_msg_manager>
|
||||||
|
class message {
|
||||||
|
public:
|
||||||
|
typedef lib::shared_ptr<message> ptr;
|
||||||
|
...
|
||||||
|
std::string m_payload;
|
||||||
|
...
|
||||||
|
};
|
||||||
|
```]
|
||||||
|
]]]]
|
||||||
|
|
||||||
|
[[8. Sending Messages][
|
||||||
|
|
||||||
|
When sending a message, websocketpp requires that the payload is
|
||||||
|
packaged in a `websocketpp::message` object using `std::string` as the
|
||||||
|
storage, or it requires a copy of the caller provided buffer by
|
||||||
|
constructing a new message object. Messages are placed onto an
|
||||||
|
outgoing queue. An asynchronous write operation runs in the background
|
||||||
|
to clear the queue. No user facing handler can be registered to be
|
||||||
|
notified when messages or frames have completed sending.
|
||||||
|
|
||||||
|
Beast doesn't allocate or make copies of buffers when sending data. The
|
||||||
|
caller's buffers are sent in-place. You can use any object meeting the
|
||||||
|
requirements of
|
||||||
|
[@http://www.boost.org/doc/html/boost_asio/reference/ConstBufferSequence.html ConstBufferSequence],
|
||||||
|
permitting efficient scatter-gather I/O.
|
||||||
|
|
||||||
|
The [*ConstBufferSequence] interface allows callers to send data from
|
||||||
|
memory-mapped regions (not possible in websocketpp). Callers can also
|
||||||
|
use the same buffers to send data to multiple streams, for example
|
||||||
|
broadcasting common subscription data to many clients at once. For
|
||||||
|
each call to `async_write` the completion handler is called once when
|
||||||
|
the data finishes sending, in a manner identical to `boost::asio::async_write`.
|
||||||
|
|
||||||
|
[table
|
||||||
|
[
|
||||||
|
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L1048 Beast]]
|
||||||
|
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L672 websocketpp]]
|
||||||
|
][
|
||||||
|
[```
|
||||||
|
template<class ConstBufferSequence>
|
||||||
|
void
|
||||||
|
write(ConstBufferSequence const& buffers);
|
||||||
|
```]
|
||||||
|
[```
|
||||||
|
lib::error_code send(std::string const & payload,
|
||||||
|
frame::opcode::value op = frame::opcode::text);
|
||||||
|
...
|
||||||
|
lib::error_code send(message_ptr msg);
|
||||||
|
```]
|
||||||
|
]]]]
|
||||||
|
|
||||||
|
[[9. Streaming Messages][
|
||||||
|
|
||||||
|
websocketpp requires that the entire message fit into memory, and that
|
||||||
|
the size is known ahead of time.
|
||||||
|
|
||||||
|
Beast allows callers to compose messages in individual frames. This is
|
||||||
|
useful when the size of the data is not known ahead of time or if it
|
||||||
|
is not desired to buffer the entire message in memory at once before
|
||||||
|
sending it. For example, sending periodic output of a database query
|
||||||
|
running on a coroutine. Or sending the contents of a file in pieces,
|
||||||
|
without bringing it all into memory.
|
||||||
|
|
||||||
|
[table
|
||||||
|
[
|
||||||
|
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L1151 Beast]]
|
||||||
|
[websocketpp]
|
||||||
|
][
|
||||||
|
[```
|
||||||
|
template<class ConstBufferSequence>
|
||||||
|
void
|
||||||
|
write_frame(bool fin,
|
||||||
|
ConstBufferSequence const& buffers);
|
||||||
|
```]
|
||||||
|
[
|
||||||
|
/<not available>/
|
||||||
|
]
|
||||||
|
]]]]
|
||||||
|
|
||||||
|
[[10. Flow Control][
|
||||||
|
|
||||||
|
The websocketpp read implementation continuously reads asynchronously
|
||||||
|
from the network and buffers message data. To prevent unbounded growth
|
||||||
|
and leverage TCP/IP's flow control mechanism, callers can periodically
|
||||||
|
turn this 'read pump' off and back on.
|
||||||
|
|
||||||
|
In contrast a `beast::websocket::stream` does not independently begin
|
||||||
|
background activity, nor does it buffer messages. It receives data only
|
||||||
|
when there is a call to an asynchronous initiation function (for
|
||||||
|
example `beast::websocket::stream::async_read`) with an associated handler.
|
||||||
|
Applications do not need to implement explicit logic to regulate the
|
||||||
|
flow of data. Instead, they follow the traditional model of issuing a
|
||||||
|
read, receiving a read completion, processing the message, then
|
||||||
|
issuing a new read and repeating the process.
|
||||||
|
|
||||||
|
[table
|
||||||
|
[
|
||||||
|
[Beast]
|
||||||
|
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L728 websocketpp]]
|
||||||
|
][
|
||||||
|
[
|
||||||
|
/<implicit>/
|
||||||
|
]
|
||||||
|
[```
|
||||||
|
lib::error_code pause_reading();
|
||||||
|
lib::error_code resume_reading();
|
||||||
|
```]
|
||||||
|
]]]]
|
||||||
|
|
||||||
|
[[11. Connection Establishment][
|
||||||
|
|
||||||
|
websocketpp offers the `endpoint` class which can handle binding and
|
||||||
|
listening to a port, and spawning connection objects.
|
||||||
|
|
||||||
|
Beast does not reinvent the wheel here, callers use the interfaces
|
||||||
|
already in `boost::asio` for receiving incoming connections resolving
|
||||||
|
host names, or establishing outgoing connections. After the socket (or
|
||||||
|
`boost::asio::ssl::stream`) is connected, the `beast::websocket::stream`
|
||||||
|
is constructed around it and the WebSocket handshake can be performed.
|
||||||
|
|
||||||
|
Beast users are free to implement their own "connection manager", but
|
||||||
|
there is no requirement to do so.
|
||||||
|
|
||||||
|
[table
|
||||||
|
[
|
||||||
|
[[@http://www.boost.org/doc/html/boost_asio/reference/async_connect.html Beast],
|
||||||
|
[@http://www.boost.org/doc/html/boost_asio/reference/basic_socket_acceptor/async_accept.html also]]
|
||||||
|
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/asio/endpoint.hpp#L52 websocketpp]]
|
||||||
|
][
|
||||||
|
[```
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
```]
|
||||||
|
[```
|
||||||
|
template <typename config>
|
||||||
|
class endpoint : public config::socket_type;
|
||||||
|
```]
|
||||||
|
]]]]
|
||||||
|
|
||||||
|
[[12. WebSocket Handshaking][
|
||||||
|
|
||||||
|
Callers invoke `beast::websocket::accept` to perform the WebSocket
|
||||||
|
handshake, but there is no requirement to use this function. Advanced
|
||||||
|
users can perform the WebSocket handshake themselves. Beast WebSocket
|
||||||
|
provides the tools for composing the request or response, and the
|
||||||
|
Beast HTTP interface provides the container and algorithms for sending
|
||||||
|
and receiving HTTP/1 messages including the necessary HTTP Upgrade
|
||||||
|
request for establishing the WebSocket session.
|
||||||
|
|
||||||
|
Beast allows the caller to pass the incoming HTTP Upgrade request for
|
||||||
|
the cases where the caller has already received an HTTP message.
|
||||||
|
This flexibility permits novel and robust implementations. For example,
|
||||||
|
a listening socket that can handshake in multiple protocols on the
|
||||||
|
same port.
|
||||||
|
|
||||||
|
Sometimes callers want to read some bytes on the socket before reading
|
||||||
|
the WebSocket HTTP Upgrade request. Beast allows these already-received
|
||||||
|
bytes to be supplied to an overload of the accepting function to permit
|
||||||
|
sophisticated features. For example, a listening socket that can
|
||||||
|
accept both regular WebSocket and Secure WebSocket (SSL) connections.
|
||||||
|
|
||||||
|
[table
|
||||||
|
[
|
||||||
|
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L501 Beast],
|
||||||
|
[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L401 also]]
|
||||||
|
[websocketpp]
|
||||||
|
][
|
||||||
|
[```
|
||||||
|
template<class ConstBufferSequence>
|
||||||
|
void
|
||||||
|
accept(ConstBufferSequence const& buffers);
|
||||||
|
|
||||||
|
template<class Body, class Headers>
|
||||||
|
void
|
||||||
|
accept(http::request_v1<Body, Headers> const& request);
|
||||||
|
```]
|
||||||
|
[
|
||||||
|
/<not available>/
|
||||||
|
]
|
||||||
|
]]]]
|
||||||
|
|
||||||
|
]
|
||||||
|
]]
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
[endsect]
|
@ -88,13 +88,8 @@ provides implementations of the HTTP and WebSocket protocols.
|
|||||||
[[
|
[[
|
||||||
[link beast.design Design]
|
[link beast.design Design]
|
||||||
][
|
][
|
||||||
Design rationale, answers to questions, and
|
Design rationale, answers to questions, library comparisons,
|
||||||
other library comparisons.
|
and special considerations for Boost Formal Review participants.
|
||||||
]]
|
|
||||||
[[
|
|
||||||
[link beast.review For Reviewers]
|
|
||||||
][
|
|
||||||
Participants in Beast's formal review should read this first.
|
|
||||||
]]
|
]]
|
||||||
[[
|
[[
|
||||||
[link beast.ref Reference]
|
[link beast.ref Reference]
|
||||||
@ -114,7 +109,6 @@ provides implementations of the HTTP and WebSocket protocols.
|
|||||||
[include websocket.qbk]
|
[include websocket.qbk]
|
||||||
[include examples.qbk]
|
[include examples.qbk]
|
||||||
[include design.qbk]
|
[include design.qbk]
|
||||||
[include review.qbk]
|
|
||||||
|
|
||||||
[section:ref Reference]
|
[section:ref Reference]
|
||||||
[xinclude quickref.xml]
|
[xinclude quickref.xml]
|
||||||
|
Reference in New Issue
Block a user