[boost] New Lib "Beast", HTTP + WebSocket protocols

509 views
Skip to first unread message

Vinnie Falco

unread,
Apr 22, 2016, 1:01:40 PM4/22/16
to bo...@lists.boost.org
"Beast"

This is a new library that implements the HTTP and WebSocket protocols
using Boost.Asio and a few other Boost tidbits. Here's a quick
synopsis of what's in it:

http::message:
- class template modeling the HTTP message with customization points
to satisfy even the most tough-minded reviewer.

http::read
http::write
http::async_read
http::async_write
- Free functions to easily send and receive HTTP messages. Modeled to
work exactly like boost::asio free functions. Role-agnostic, build
clients or servers!

websocket::stream
- class template wrapping any asio sync or async stream. Implements
the WebSocket protocol including the initial handshake.

Examples:
- websocket echo servers
- HTTP servers for simple websites (no cgi)

"I've heard enough, show me example code!"
http://vinniefalco.github.io/beast/beast/intro/example.html

Main page, with links to GitHub repository, HTML documentation and benchmarks:
http://vinniefalco.github.io/

Feedback welcome, the author checks email and Issues on the GitHub
repository. This project has been submitted to the Boost incubator.

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

David Stone

unread,
Apr 22, 2016, 8:13:54 PM4/22/16
to bo...@lists.boost.org
How does this compare to www.zaphoyd.com/websocketpp

Vinnie Falco

unread,
Apr 22, 2016, 9:23:21 PM4/22/16
to bo...@lists.boost.org
On Fri, Apr 22, 2016 at 8:13 PM, David Stone <da...@doublewise.net> wrote:
> How does this compare to www.zaphoyd.com/websocketpp

We have experience with websocketpp as it is the solution we have been
using in our open source peer to peer software
(http://github.com/ripple/rippled) for the last five years. It is that
experience that drove the development of this new library as its
replacement. Since the WebSocket protocol starts with a HTTP
handshake, we also developed Beast.HTTP as an added bonus - think of
it as a double feature at the drive-in.

Beast.WebSocket has these things going for it:

* Construct a stream from a socket or ssl::stream that's already
connected or accepted:

boost::asio::ip::tcp::socket sock(ios);
websocket::stream<boost::asio::ip::tcp::socket> ws(std::move(sock));

* Accept a WebSocket handshake request with bytes that have already
been read from the socket:

template<class ConstBufferSequence>
void websocket::stream::accept(ConstBufferSequence const& buffers);

* Accept a WebSocket HTTP request handshake that was already parsed
(e.g. from Beast.HTTP):

template<class Body, class Headers>
void websocket::stream::accept(http::request<Body, Headers> const& request);

* Wraps any object meeting these type requirements, use your own type
if you want:

websocket::stream<YourTypeHere> ws(...);

The type requirements (sync, async, or both):
http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/SyncReadStream.html
http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/SyncWriteStream.html
http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/AsyncReadStream.html
http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/AsyncWriteStream.html

* Receive functions accept any object meeting the requirements of
Streambuf, permitting custom memory management strategies including
the use of statically sized buffers. For example games using messages
of fixed length.

Streambuf type requirements:
http://vinniefalco.github.io/beast/beast/types/Streambuf.html

* In the Asio doc, Christopher Kohlhoff alludes to alternate
implementation strategies for boost::asio::streambuf using multiple
discontiguous buffers of varying sizes. We fulfill this idea with an
optimized stream buffer that follows the strategy he described,
providing a useful building block for sending and receiving variable
size data. See:

https://github.com/vinniefalco/Beast/blob/master/include/beast/basic_streambuf.hpp

* Interfaces are designed to closely resemble Asio in every possible
way. This includes support for the extensible asynchronous model -
support for std::future and stackful/stackless coroutines right out of
the box. The library is designed to eliminate the learning curve for
those familiar with Asio:

extern void handle_read(boost::system::error_code ec);

websocket::stream<boost::asio::ip::tcp::socket> ws(ios);
...
websocket::opcode op;
beast::streambuf sb;
ws.async_read(op, sb, std::bind(&handle_read, std::placeholders::_1));

* All functions are template driven so everything is transparent to
the compiler, including completion handlers passed to asynchronous
initiation functions and objects used as buffers.

By design, Beast.WebSocket avoids doing these things:

* Managing the io_service
* Managing a listening socket
* Managing threads or a thread pool
* Requiring the use of exceptions in certain places
* Imposing a particular message buffering strategy
* Using std::function for asynchronous callbacks
* Requiring callers to know the size of the message in advance.

The last point above is especially important for applications that
produce data incrementally. For example, a huge JSON reply that comes
from a database query.

Planned improvements for Beast.WebSocket include:

* Explicit proxy support with basic authentication
* Tools for parsing subprotocols and message headers
* permessage compression:
https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-28

Years of experience working with Asio and and WebSockets have gone
into the development of this library. It tries to be a natural
extension of Asio, in a manner that is as narrow and lean as possible
to provide maximum flexibility and performance.

Bjorn Reese

unread,
Apr 24, 2016, 7:11:05 AM4/24/16
to bo...@lists.boost.org
On 04/22/2016 07:01 PM, Vinnie Falco wrote:

> Main page, with links to GitHub repository, HTML documentation and benchmarks:
> http://vinniefalco.github.io/
>
> Feedback welcome, the author checks email and Issues on the GitHub
> repository. This project has been submitted to the Boost incubator.

This looks like a nice library. You have clearly encountered the same
pain points as I while working with other HTTP-based libraries, and you
seem to have come up with solutions similar to those found in the
Boost.Http project [1].

It may be a good idea if we joined the two projects together. Would you
consider such an option?

Last year, the Boost.Http project went through a formal Boost review.
The summary [2] shows what the Boost community expects from a HTTP
library. In order to address one of these expectations, we have a
Boost Summer of Code project this year for a HTTP parser.


[1] https://github.com/BoostGSoC14/boost.http
[2] http://lists.boost.org/boost-announce/2015/08/0452.php

Vinnie Falco

unread,
Apr 24, 2016, 9:15:03 AM4/24/16
to bo...@lists.boost.org
On Sun, Apr 24, 2016 at 7:09 AM, Bjorn Reese <bre...@mail1.stofanet.dk> wrote:
> This looks like a nice library...

Thanks for the kind words!

> It may be a good idea if we joined the two projects together. Would you
> consider such an option?

I'm open to collaboration but also cautious. Beast.HTTP was designed
throughout to have a narrow interface. It offers only a universal
model for the HTTP message, and functions to parse, serialize,
deserialize, and send/receive on sockets. It offers both synchronous
and asynchronous functionality. And it accomplishes these goals with
an interface that resembles Boost.Asio as closely as possible, to
eliminate the learning curve for using the library.

It is this author's opinion that the more a library tries to do, the
more controversial it is and the harder it will be to get through the
boost review process. There's a strong need for simple free functions
to send and receive HTTP messages on a socket in as few lines of code
as possible. Beast.HTTP achieves this. Example code:

using namespace beast::http;
boost::asio::ip::tcp::socket sock(ios);
...
request<empty_body> req({method_t::http_get, "/", 11});
req.headers.replace("Host", "boost.org:80");
req.headers.replace("User-Agent", "Beast.HTTP");
write(sock, req);

Full, compiling example program here:
http://vinniefalco.github.io/beast/beast/intro/example.html

Remember that Beast includes both HTTP and WebSocket. The HTTP support
was driven by the need of the websocket stream to perform a HTTP
Upgrade handshake when initializing the session. I think a litmus test
for the interface quality of a HTTP library, is the ease in which it
allows an implementation to perform this websocket handshake. Here is
actual code from beast::websocket::stream which synchronously performs
the WebSocket HTTP Upgrade handshake:

template<class NextLayer>
void stream<NextLayer>::
handshake(boost::string_ref const& host,
boost::string_ref const& resource, error_code& ec)
{
std::string key;
http::write(stream_,
build_request(host, resource, key), ec);
if(ec)
return;
http::response<http::string_body> resp;
http::read(next_layer_, stream_.buffer(), resp, ec);
if(ec)
return;
do_response(resp, key, ec);
}

The function above is not possible with Boost.Http in its current
incarnation, as there is no client support nor is there support for
synchronous operations. Based on the design choices visible at the tip
of trunk (for example, requiring a stream constructed around the
socket to send and receive messages) it seems unlikely that such
simple operations will be possible in future incarnations of that
library unless major changes in its design are made.

> Last year, the Boost.Http project went through a formal Boost review.
> The summary [2] shows what the Boost community expects from a HTTP
> library.

I'll quote and address each of the points brought up in [2], the
feedback from the review of Boost.Http:

"Boost.Http currently only provides a server-side API, but the
reviewers felt that a client-side API would be usable to more users."

Beast.HTTP is completely role-agnostic, and works for building clients
and servers.

"There was also a recurring request for Boost.Http to be a header-only
library. The HTTP parser currently used is not header-only and that
is the main obstacle towards a header-only Boost.Http library."

Agree 100%. Beast.HTTP also uses the NodeJS parser, and that's the
only bit of code that is not header only. Thursday I started on a
header-only parser, here's what we have so far (please keep in mind,
this is a work in progress):
https://github.com/vinniefalco/rippled/blob/my-parser/src/beast/test/http/my_parser.cpp

This parser will not have any dependencies except the standard library
so if necessary the code could be pinched for other projects.

"Some of the discussion revolved around what level of abstraction
would be appropriate for Boost.Http. The views ranged from wanting
higher- level APIs..."

Higher-level APIs are great but there is danger in offering increasing
levels of abstraction. At each increase, the target audience
diminishes and the chances of design choices made in the abstraction
becoming inappropriate for particular use-cases goes up.

No matter how strong the desire for higher level APIs, they need to be
built on a foundation. Beast.HTTP provides the correct foundation; it
is that which cannot be broken down further and it is that upon which
everything else can be built. As such, it offers library virtue with
its current feature set.

"...all the way down to simply wanting a HTTP parser/generator and
then leave all the socket and buffer management up to the user."

By design, Beast.HTTP does not perform any buffering or socket
management. Such layers can be built on top; one of the examples
creates a pipelining stream, see:
https://github.com/vinniefalco/Beast/blob/master/examples/http_stream.h
https://github.com/vinniefalco/Beast/blob/master/examples/http_async_server.h

"Boost.Http currently creates an associative array for all header
fields. One reviewer explored the idea of using an incremental (push
or pull) HTTP parser as part of the API to let users decide which
header fields to copy and which to discard."

The Beast.HTTP read algorithm allows customization of the Parser
template argument (*), allowing any type that meets the requirements
to be used as the implementation for parsing messages.

(*) planned feature

"Some reviewers also felt that HTTP/2 should be part of Boost.Http,
partly because that would demonstrate the extensibility of the current
design, and partly because the library would be in a stronger
position to attract users if it offers more than its competitors."

The IETF adopted as a goal for HTTP/2, to leave the message the same
(while changing its wire format). Therefore, Beast.HTTP's message
model is already HTTP/2-friendly.

As for the extensibility of the design, free functions to send and
receive HTTP messages are fundamentally incompatible with HTTP/2,
which requires ongoing
state to decompress headers and multiplex streams. And yet, we know
that free functions to send and receive HTTP/1 messages are useful and
sorely needed.

Furthermore HTTP/2 adds features that don't make sense for HTTP/1.
What would you do with the stream ID? It is not part of the message
model, because it describes a transport level property (and what
meaning would it have for a HTTP/1 message? or someone who is using
the message object but not using any sockets at all?) What about the
interface to inform the peer of a new receive window size? What about
setting stream priorities/weights? How do we sort all this out?

The interface for HTTP/1 should consist of a universal message model,
plus functions to send and receive those messages on sockets. The
interface for HTTP/2 should be a class template that wraps a socket
(similar in style to beast::websocket::stream), deals in the same
universal message model, and offers member functions to send and
receive those messages with associated stream IDs as well as adjust
HTTP/2-specific session parameters.

We respectfully disagree with those reviewers who feel that a single
interface should serve the needs of sending and receiving both HTTP/1
and HTTP/2 messages.

> In order to address one of these expectations
> we have a Boost Summer of Code project this year for a HTTP parser.

Hopefully this header-only parser will be done before the summer begins.

One area where Beast.HTTP could use work is in the examples. Another,
bigger project would be to develop a HTTP/2 stream class template for
Beast using its message model. These are areas where contributions
would be most welcomed and likely the most productive use of external
effort.

Vinícius dos Santos Oliveira

unread,
Apr 24, 2016, 3:33:42 PM4/24/16
to Boost
2016-04-24 10:14 GMT-03:00 Vinnie Falco <vinnie...@gmail.com>:

> I'm open to collaboration but also cautious.
>

That's good to know.

From the rest of your reply, I assume Boost.Http isn't ready to be
integrated right now.

I wish you could put more details in the documentation, so I can understand
Beast better without delving into the code.

Maybe I'll bug you more later once Boost.Http advances a little more. And,
like Bjorn already stated, nice library.

There's a strong need for simple free functions
> to send and receive HTTP messages on a socket in as few lines of code
> as possible. Beast.HTTP achieves this. Example code:
>
> using namespace beast::http;
> boost::asio::ip::tcp::socket sock(ios);
> ...
> request<empty_body> req({method_t::http_get, "/", 11});
> req.headers.replace("Host", "boost.org:80");
> req.headers.replace("User-Agent", "Beast.HTTP");
> write(sock, req);
>

You explained your point quite well.

Could you please write an example of how do you imagine the ideal
server-side API?

"Boost.Http currently only provides a server-side API, but the
> reviewers felt that a client-side API would be usable to more users."
>
> Beast.HTTP is completely role-agnostic, and works for building clients
> and servers.
>

We hope to develop the client-side API after the parser project is
finished. The same abstractions should be used too. However, it may be
useful to add client-side only useful functions where we can have
specialized get or post methods.

Bjorn is wrapping libcurl into a Boost-like interface to gain knowledge:
https://github.com/breese/trial.http.curl

"There was also a recurring request for Boost.Http to be a header-only
> library. The HTTP parser currently used is not header-only and that
> is the main obstacle towards a header-only Boost.Http library."
>
> Agree 100%. Beast.HTTP also uses the NodeJS parser, and that's the
> only bit of code that is not header only. Thursday I started on a
> header-only parser, here's what we have so far (please keep in mind,
> this is a work in progress):
>
> https://github.com/vinniefalco/rippled/blob/my-parser/src/beast/test/http/my_parser.cpp
>

I wasn't aware of this effort before.

I took a look now that you mentioned. From what I've seen, I assume that
this parser has a SAX-like interface where you interact through callbacks.

The Boost.Http parser that will be developed within this summer will be a
pull parser. It has a less intrusive design and can be used to build parser
with SAX-like interfaces or DOM-like interfaces. It should be useful enough
to also be used as is (i.e. without wrapping in another interface). It
should also be easy to expose iterators using this parser and allow us to
reuse all STL algorithms.

You can take a look at this parser's proposal at
https://gist.github.com/vinipsmaker/4998ccfacb971a0dc1bd .

You can see an example of a pull parser at
https://github.com/google/pulldown-cmark .

--
Vinícius dos Santos Oliveira
https://vinipsmaker.github.io/

Vinnie Falco

unread,
Apr 24, 2016, 5:35:04 PM4/24/16
to bo...@lists.boost.org
On Sun, Apr 24, 2016 at 3:33 PM, Vinícius dos Santos Oliveira
<vini.i...@gmail.com> wrote:
> I wish you could put more details in the documentation, so I can understand
> Beast better without delving into the code.

Agreed. The HTTP side can definitely use more documentation. Since the
announcement I added a new page which explains things step by step:
http://vinniefalco.github.io/beast/beast/http/usage.html

> like Bjorn already stated, nice library.

Thanks, it is appreciated!

> Could you please write an example of how do you imagine the ideal
> server-side API?

Well, being role-agnostic means the same functions are used by both
clients and servers. The only meaningful difference is that a server
will receive request objects and send response objects instead of the
other way around. The message class template distinguishes requests
and responses using a bool "isRequest" template argument. These
statements each declare a response:

http::message<false, http::string_body> resp;
http::response<http::string_body> resp2;

Both of these declarations are identical, http::response is merely a
type alias for http::message with the bool "isRequest" set to true.

The same functions are used by both clients and servers to read and
write messages:

// synchronous
http::write(sock, resp);

// asynchronous
void handle_write(boost::system::error_code);
..
http::async_write(sock, resp,
std::bind(&handle_write, std::placeholders::_1));

Beast comes with an example program that implements a basic web
server, capable of serving simple HTML, in both synchronous and
asynchronous forms:
https://github.com/vinniefalco/Beast/blob/master/examples/http_sync_server.h
https://github.com/vinniefalco/Beast/blob/master/examples/http_async_server.h

> ...it may be useful to add client-side only useful functions where we can


> have specialized get or post methods.

These definitely sound like useful operations, and they should be
available at some interface level. But its not clear that they they
are sufficiently general purpose as to merit inclusion in a library
that tries to satisfy everyone. No matter how specialized the get or
post method there still needs to be a way to package the message up in
a first-class type and send or receive it; Beast provides the means to
do that.

>> Thursday I started on a header-only parser,
>

> I wasn't aware of this effort before.

Its a minor effort, likely not worthy of fanfare.

> I took a look now that you mentioned. From what I've seen, I assume that
> this parser has a SAX-like interface where you interact through callbacks.

I'm writing something that functions very similarly in style to the
nodejs-http-parser, but updated for C++. The goal here is to eliminate
a blemish on Beast, that it is not completely header-only. It inherits
the zero-memory / zero-copy design of nodejs-http-parse to retain as
much of its performance as possible (although, it does away with
architecture-specific branch prediction hints). Some of the design
improvements I'm making:

* Users derive from the parser base class using CRTP (Curiously
Recurring Template Pattern)
* Callbacks are optional, detected through SFINAE
* Callbacks are made to the derived class
* The callbacks are transparent to the compiler (i.e. no function
pointers), allowing inlining
* No macros or dependence on preprocessor directives
* No dependencies except for boost::string_ref; easily reused in other projects
* Random HTTP-message generator for fuzzing

> The Boost.Http parser that will be developed within this summer will be a
> pull parser. It has a less intrusive design and can be used to build parser
> with SAX-like interfaces or DOM-like interfaces. It should be useful enough
> to also be used as is (i.e. without wrapping in another interface).

Beast doesn't try to offer a universal or flexible parser, it just
offers a parser that gets users reading messages right out of the box,
and is sufficiently robust and performant as to make it a competitive
choice for implementing production-class servers.

Beast's HTTP message reading implementation is general purpose,
callers can provide their own Parser template argument that meets the
type requirements (*), permitting alternate implementation strategies.
For example, keeping only the headers you care about. Or using a
perfect hash function to decode the field name to an enum. This works
hand in hand with customizing the Headers parameter in the message
class template argument.

(*) planned feature

> It should also be easy to expose iterators using this parser and allow us to
> reuse all STL algorithms.

Since the whole thing is now templated it might be practical to revise
the interface to accept a Boost.Range of chars and work with iterators
in the fashion you described. That could be the subject of a future
improvement.

Vinícius dos Santos Oliveira

unread,
Apr 24, 2016, 6:44:20 PM4/24/16
to Boost
2016-04-24 18:34 GMT-03:00 Vinnie Falco <vinnie...@gmail.com>:

> > Could you please write an example of how do you imagine the ideal
> > server-side API?
>
> Well, being role-agnostic means the same functions are used by both
> clients and servers. The only meaningful difference is that a server
> will receive request objects and send response objects instead of the
> other way around. The message class template distinguishes requests
> and responses using a bool "isRequest" template argument. These

> statements each declare a response: [...]
>

This makes me think that the two projects are more alike than you might
think.

The main difference here is just that Boost.Http's Message don't carry
request-exclusive members (HTTP verb, uri) or response-exclusive members
(status code, status messages) and methods that work on them take them
separately. I'm open to follow a design more similar to yours (just need to
discuss with Bjorn and other members first as I remember this decision was
taken to follow a more fundamental message-based model).

Another main difference is that Boost.Http server side design is really
concerned about different HTTP backends. However, this is only observed in
details and I really doubt they'll bother you. At most, I need to implement
more convenient functions to remove the amount of boilerplate needed today
(already on the TODO list and on the GitHub issue tracker).

I believe the rest of the differences are really minor.

Of course I might be wrong and I'll know once more information about the
project is exposed (documentation maybe).

"Some reviewers also felt that HTTP/2 should be part of Boost.Http,
> partly because that would demonstrate the extensibility of the current
> design, and partly because the library would be in a stronger
> position to attract users if it offers more than its competitors."
>
> The IETF adopted as a goal for HTTP/2, to leave the message the same
> (while changing its wire format). Therefore, Beast.HTTP's message
> model is already HTTP/2-friendly.
>

> As for the extensibility of the design, free functions to send and


> receive HTTP messages are fundamentally incompatible with HTTP/2,
> which requires ongoing
> state to decompress headers and multiplex streams. And yet, we know
> that free functions to send and receive HTTP/1 messages are useful and
> sorely needed.
>
> Furthermore HTTP/2 adds features that don't make sense for HTTP/1.
> What would you do with the stream ID? It is not part of the message
> model, because it describes a transport level property (and what
> meaning would it have for a HTTP/1 message? or someone who is using
> the message object but not using any sockets at all?) What about the
> interface to inform the peer of a new receive window size? What about
> setting stream priorities/weights? How do we sort all this out?
>
> The interface for HTTP/1 should consist of a universal message model,
> plus functions to send and receive those messages on sockets. The
> interface for HTTP/2 should be a class template that wraps a socket
> (similar in style to beast::websocket::stream), deals in the same

> universal message model, and offers member functions to send and


> receive those messages with associated stream IDs as well as adjust
> HTTP/2-specific session parameters.
>
> We respectfully disagree with those reviewers who feel that a single
> interface should serve the needs of sending and receiving both HTTP/1
> and HTTP/2 messages.
>

But the two interfaces could be very similar. We also don't need force the
user to mess with stream IDs.

Server push is another story and indeed needs new APIs.

--
Vinícius dos Santos Oliveira
https://vinipsmaker.github.io/

_______________________________________________

Vinnie Falco

unread,
Apr 28, 2016, 7:47:09 PM4/28/16
to bo...@lists.boost.org
The authors of Boost.Http and Beast (Vinícius dos Santos Oliveira and
Vinnie Falco respectively) held a 4 hour summit on Thursday April 28,
discussing the two projects on Google Hangout/Skype, with a
simultaneous live-coding window (hosted for free by
http://coderpad.io).

The discussion began with defining the role of Beast. Based on
feedback from IRC and Vinícius, I realized that Beast is very much a
low level library and does not try to satisfy end users with
convenient one liners for doing common tasks like fetching a webpage
from a server. We formalized that role for Beast, defining these areas
of responsibility for the library:

1. Define a universal HTTP message model
2. Parse messages in HTTP/1.* wire format
3. Read HTTP/1.* messages from a socket/stream
4. Serialize messages in HTTP/1.* wire format
5. Write HTTP/1.* message to a socket/stream

While these interfaces are essential building blocks they don't
directly satisfy the use-cases that most users want. Boost.Http aims
to fill those gaps by providing higher level abstractions. For
example, its http server offering manages common tasks such as setting
ETag on replies, serving files asynchronously, and managing connection
state.

I introduced a new class, beast::http::basic_parser which is a
header-only HTTP parser in the style of the nodejs parser written in C
commonly used by these libraries, but updated to use C++ features like
CRTP and templating. This now-complete implementation allows existing
and new libraries to migrate off the nodejs parser and become
header-only offerings.

Discussions then moved to refinement of the BasicParser concept and
its role in deserializing messages. We discussed the Body concept and
its associated types permitting customization of serialization and
deserialization strategies.

Finally we talked about what a collaboration might look like. We
discussed the pros and cons of merging the projects, its effect on
development, documentation, tests, and the disposition of the
WebSocket portion of Beast. The conclusion for now is to explore Beast
integration in Boost.Http, where Beast's role is to provide the low
level operations for serializing, deserializing HTTP/1.1 to sockets
and streams. Beast could be incorporated as a submodule for now, with
tighter integration choices considered later.

We ended with a few calls to action:

* Changes to Beast message interfaces to facilitate integration
* Explore integration of beast::http::basic_parser into Boost.Http

Thanks

On Fri, Apr 22, 2016 at 1:01 PM, Vinnie Falco <vinnie...@gmail.com> wrote:
> "Beast"
>
> This is a new library that implements the HTTP and WebSocket protocols
> using Boost.Asio and a few other Boost tidbits. Here's a quick
> synopsis of what's in it:

_______________________________________________

Hartmut Kaiser

unread,
Apr 29, 2016, 8:32:04 AM4/29/16
to bo...@lists.boost.org

> On Fri, Apr 22, 2016 at 8:13 PM, David Stone <da...@doublewise.net> wrote:
> > How does this compare to www.zaphoyd.com/websocketpp
>
> We have experience with websocketpp as it is the solution we have been
> using in our open source peer to peer software
> (http://github.com/ripple/rippled) for the last five years. It is that
> experience that drove the development of this new library as its
> replacement. Since the WebSocket protocol starts with a HTTP
> handshake, we also developed Beast.HTTP as an added bonus - think of
> it as a double feature at the drive-in.

Thanks for the lengthy explanation of your library features. However to get
back to the question originally asked (which you haven't really answered):
'How does this compare to www.zaphoyd.com/websocketpp'? Why should I use it
and not websocketpp?

Thanks!
Regards Hartmut
---------------
http://boost-spirit.com
http://stellar.cct.lsu.edu

Vinnie Falco

unread,
Apr 29, 2016, 12:49:54 PM4/29/16
to bo...@lists.boost.org
On Fri, Apr 29, 2016 at 8:31 AM, Hartmut Kaiser
<hartmut...@gmail.com> wrote:
> How does this compare to www.zaphoyd.com/websocketpp'?
> Why should I use it and not websocketpp?

You should use Beast instead of websocketpp if: Beast has the features
you need (compression and subprotocols planned), and you care about
any of the library differences in the section that follows.

First, websocketpp is a great library; its clear the author paid a lot
of attention to implementing broad support for websocket including
Hixie-76 which is not a feature found in Beast. That library has great
documentation, support, and quite a few years of presence. In this
author's opinion it is one of the best available WebSocket
implementation in C++, and by a wide margin.

Features already in websocketpp but planned for Beast include
per-message compression and utilities for analyzing subprotocols. The
section that follows provides a feature by feature comparison of
websocketpp and Beast, with links to relevant source material and then
exposition.



1. Synchronous Interfaces

websocketpp:
<not available>

beast:
template<class Streambuf>
void
read(opcode& op, Streambuf& streambuf)

https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L774

Beast offers full support for websocket using a synchronous interface.
It uses the same style of synchronous interfaces found in Boost.Asio:
versions that throw exceptions, or versions that return the error code
in a reference parameter.



2. Connection Model

websocketpp:
template <typename config>
class connection
: public config::transport_type::transport_con_type
, public config::connection_base
{
public:
typedef lib::shared_ptr<type> ptr;
...

https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L234

beast:
template<class NextLayer>
class stream : public detail::stream_base
{
NextLayer next_layer_;
...

https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp

websocketpp supports multiple transports by utilizing a trait, the
config::transport_type. An example of the implementation of this
concept is the asio transport, declared here:
https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/asio/connection.hpp#L60
To get an idea of the complexity involved with implementing a
transport, compare the asio transport to the iostream transport (a
layer that allows websocket communication over a std iostream):
https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/iostream/connection.hpp#L59

In contrast, beast abstracts the transport by defining just one
template argument, the NextLayer. The type requirements for NextLayer
are already familiar to users as they are documented in Asio:
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::socket. Callers can put such objects
in a shared_ptr if they want to, but there is no requirement to do so.



3. Client and Server Roles

websocketpp:
template <typename config>
class client : public endpoint<connection<config>,config>;
template <typename config>
class server : public endpoint<connection<config>,config> {

https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/roles/server_endpoint.hpp#L39
https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/roles/client_endpoint.hpp#L42

beast:
<not applicable>

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.



4. Thread Safety

websocketpp:
mutex_type m_read_mutex;

https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/iostream/connection.hpp#L706

beast:
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);
}

https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/impl/read_frame_op.ipp#L118

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:
http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/asio_handler_invoke.html

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's value for NextLayer is single threaded,
this counts as an implicit strand and no performance costs associated
with mutexes are incurred.



5. Callback Model

websocketpp:
typedef lib::function<void(connection_hdl,message_ptr)> message_handler;
void set_message_handler(message_handler h);

https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L281
https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L473

beast:
template<class Streambuf, class ReadHandler>
typename async_completion<ReadHandler, void(error_code)>::result_type
async_read(opcode& op, Streambuf& streambuf, ReadHandler&& handler);

https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L834

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 customizations, continuation
customization, or invoke customization 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.



6. Extensible Asynchronous Model

websocketpp:
<not available>

beast:
...
beast::async_completion<ReadHandler, void(error_code)> completion(handler);
read_op<Streambuf, decltype(completion.handler)>{
completion.handler, *this, op, streambuf};
return completion.result.get();

https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/impl/stream.ipp#L378

Beast fully supports the Extensible Asynchronous Model developed by
Christopher Kohlhoff, author of Boost.Asio. See Section 8 in
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3896.pdf

This means that Beast websocket asynchronous interface can be used
with std::future, stackful/stackless coroutines, or user defined
customizations.



7. Message Buffering

websocketpp:
template <template<class> class con_msg_manager>
class message {
public:
typedef lib::shared_ptr<message> ptr;
...
std::string m_payload;

https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/message_buffer/message.hpp#L78

beast:
template<class Streambuf>

https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L834
http://vinniefalco.github.io/beast/beast/types/Streambuf.html

websocketpp defines a message buffer, passed in arguments by
shared_ptr, and an associated message manager which permits
aggregation and memory 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
Streambuf (modeled after boost::asio::streambuf) and described here:
http://vinniefalco.github.io/beast/beast/types/Streambuf.html

Beast comes with the class beast::basic_streambuf, an efficient
implementation of the Streambuf 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_streambuf provided by beast supports standard allocators through
a template argument. Use the Streambuf that comes with beast,
customize the allocator if you desire, or provide your own type that
meets the requirements:
https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/basic_streambuf.hpp#L21



8. Sending Messages

websocketpp:
lib::error_code send(std::string const & payload,
frame::opcode::value op = frame::opcode::text);
...
lib::error_code send(message_ptr msg);

https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L672

beast:
template<class ConstBufferSequence, class WriteHandler>
typename async_completion<WriteHandler, void(error_code)>::result_type
async_write(ConstBufferSequence const& buffers, WriteHandler&& handler);

https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L1048

When sending a message, websocketpp requires that the payload is
packaged in a websocketpp::message object using std::string as the
storage, or it makes 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 and copy buffers when sending data. The callers
buffers are sent in-place. You can use any object meeting the
requirements of ConstBufferSequence, permitting efficient
scatter-gather I/O:
http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/ConstBufferSequence.html

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.



9. Streaming Messages

websocketpp:
<not available>

beast:
template<class ConstBufferSequence, class WriteHandler>
typename async_completion<WriteHandler, void(error_code)>::result_type
async_write_frame(bool fin,
ConstBufferSequence const& buffers, WriteHandler&& handler);

https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L1151

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.



10. Flow Control

websocketpp:
lib::error_code pause_reading();

https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L728

beast:
<implicit>

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 off the read pump. 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.



11. Connection Establishment

websocketpp:
template <typename config>
class endpoint : public config::socket_type;

https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/asio/endpoint.hpp#L52

beast:
<boost::asio>

http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/async_connect.html
http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/basic_socket_acceptor/async_accept.html

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.

---

The design choices of Beast.WebSocket were made to give users the
familiar Asio interface while leveraging its strengths, to create a
library that is lean, easy to understand, and doesn't duplicate
functionality already possible in Asio. We hope that we've succeeded
in this goal.

Hartmut Kaiser

unread,
Apr 29, 2016, 5:50:57 PM4/29/16
to bo...@lists.boost.org
Reply all
Reply to author
Forward
0 new messages