This is my review of the Beast library. I. DESIGN --------- The design of the library is very good and appears mature. The concepts are well specified, well documented, well thought out. The use of concept checks is a nice touch and a mark of professionalism. I think that basing the library on ASIO (resp. the Networking TS) is a fine decision. This does mean that if one finds an ASIO/NetTS concept less than ideal, one has to live with it, but that's an acceptable tradeoff. Severing the ties with ASIO based on the yet-hypothetical "structured blobs in, structured blobs out" model may sound good in theory, but in practice, a supermajority of the potential users of Beast need to produce a working HTTP or Websocket server, and the library as it stands addresses this need. The design is not perfect; at times the library makes it much too easy for asymptomatic mistakes to be introduced by the omission of a required member function call. For example, if one forgets to set the Content-Length, everything will still appear to work; or if one forgets to initialize the .result of the response, it remains uninitialized. (Note that these, and subsequent, observations appertain to the library as submitted for review; they may no longer be relevant since the author is very quick with the fixes.) There is, at times, unnecessary verbosity, even in the two storefront examples in http://vinniefalco.github.io/stage/beast/review/beast/quick_start.html that are supposed to show the library in the best possible light. For instance, // Set up an HTTP GET request message http::requesthttp::string_body req; req.method(http::verb::get); req.target("/"); req.version = 11; Every request will need a method and a target, so having to call members to set them feels unnecessary. A constructor ought to have been provided. Similarly, in the next example, // WebSocket says that to close a connection you have // to keep reading messages until you receive a close frame. // Beast delivers the close frame as an error from read. // beast::drain_buffer drain; // Throws everything away efficiently for(;;) { // Keep reading messages... ws.read(drain, ec); // ...until we get the special error code if(ec == websocket::error::closed) break; // Some other error occurred, report it and exit. if(ec) return fail("close", ec); } the drain loop is basically a requirement for every correct program, so it should have been encapsulated into a helper function. (Even though the example as written does illustrate the use of a drain_buffer.) I'm not particularly fond of the design decision to declare parts that in practice every user of the library will need as "out of scope". People should not be made to reinvent these wheels. I understand the aim of producing a standard library proposal, and that some of these wheels would not be suitable for standardization, but there's nothing easier than just including the suitable wheels in the formal proposal and omitting the unsuitable ones. In practice, these necessary parts end up as examples, so one has to #include "../example/wheel.hpp" instead of "beast/wheel.hpp". It would be better, in my opinion, if one includes, say, "beast/ext/wheel.hpp", with it being known that ext/ is where "out of scope" things go. The ASIO coupling did leave me with one question, whether it would be possible for the library to accommodate OS-specific file to socket transfers such as the one provided by TransmitFile: https://msdn.microsoft.com/en-us/library/windows/desktop/ms740565(v=vs.85).a... The idea here is that the OS kernel can dump the file to the socket without involving user mode, which has the potential of much increased performance. For the median user, this would not make much of a difference, but it's an interesting design question, and perhaps an argument that the example file_body class ought to be part of the library proper. ("Is in scope", to use the preferred terminology.) II. IMPLEMENTATION ------------------ I did not pay close attention to the implementation. A random sampling reveals that it's professionally written, and it appears to work, which is good enough for me. The tests run when `b2` is invoked in `test`, have good coverage, and pass with MSVC 14.0 and 14.1. Travis/Appveyor are put to good use. The library provides a doc/Jamfile, which is a plus, but this Jamfile fails, which is a minus. The file reference.qbk has its own separate build script, and this would need to be fixed one way or another when the library is integrated into the Boost documentation build. III. DOCUMENTATION ------------------ The documentation is good. The non-reference parts are very good, the examples are informative. The reference is very good at times, and not so much at others. This is caused by the fact that to get to the good parts one needs to drill down a level or two. For instance, looking at http://vinniefalco.github.io/stage/beast/review/beast/quickref.html I noticed the "async_teardown" function in the WebSocket section, so I clicked on it to see if I need to call that to tear down a WebSocket connection. This lead me to http://vinniefalco.github.io/stage/beast/review/beast/ref/beast__websocket__... which only says "Start tearing down a boost::asio::ssl::stream/connection/boost::asio::ip::tcp::socket." Googling told me that I'm not the only one left not entirely satisfied with these descriptions: https://github.com/vinniefalco/Beast/issues/274 The confusion here stems from the fact that would I, and the issue submitter, have clicked on one of the async_teardowns in that page, we would have reached f.ex. http://vinniefalco.github.io/stage/beast/review/beast/ref/beast__websocket__... which is indeed fine, and mentions that the implementation calls this function, not the user. This is a general pattern with the reference. The leaves are perfectly fine, but the upper levels are often uninformative. IV. PRACTICAL USE ----------------- As part of conducting this review, I ported two existing servers of mine to Beast, one HTTP, one WebSocket. The HTTP server can be seen at https://github.com/pdimov/beast_tpc_server and the WebSocket one is at https://github.com/pdimov/beast_ws_async_server The experience was pleasant and things went more or less as expected, with virtually no nasty surprises. The author was very helpful. The library proper worked exactly as advertised, and the servers were both easier to write than the originals, and became more robust. I only wish that more out of scope things (target parser, basic authorization handling) become in-scope, because their absence is being felt. V. VERDICT ---------- Beast should be ACCEPTED. It's useful, it works, it serves a legitimate need, and I see no design or implementation problems that would preclude acceptance.
On Mon, Jul 3, 2017 at 4:23 PM, Peter Dimov via Boost
Every request will need a method and a target, so having to call members to set them feels unnecessary. A constructor ought to have been provided. will fix
the drain loop is basically a requirement for every correct program, so it should have been encapsulated into a helper function. (Even though the example as written does illustrate the use of a drain_buffer.) will fix
I'm not particularly fond of the design decision to declare parts that in practice every user of the library will need as "out of scope".
The ASIO coupling did leave me with one question, whether it would be possible for the library to accommodate OS-specific file to socket transfers such as the one provided by TransmitFile:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms740565(v=vs.85).a...
The answer is: YES.
I have developed a working prototype writing HTTP messages to streams
synchronously and asynchronously using ::TransmitFile. This is
accomplished by providing class file_body_win32 which becomes aliased
to file_body on Windows. Then, overloads of the write functions are
provided which work on messages with the file_body type. These write
functions require boost::asio::basic_stream_socket<Protocol> for the
stream, otherwise ::TransmitFile cannot be used as there is no native
SOCKET object available (the implementation resorts to the regular
write in this case).
This behavior is entirely transparent to users. You can declare:
http::request
participants (2)
-
Peter Dimov
-
Vinnie Falco