-
Notifications
You must be signed in to change notification settings - Fork 641
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ecorm's List of Pain Points #2798
Comments
Closing immediately as these are not bugs. |
Some of these points are indeed valid.
Thanks |
I need to use the socket this way to distinguish idle timeouts from read timeouts. |
Yes these things could be documented, please open an issue |
Bumped issue #2730 I had already opened and gave it a better name. |
Added points number 7 thru 9. |
Elaborating on point 6, here are the helper classes I had to write in order to get the same namespace detail
{
//------------------------------------------------------------------------------
class HttpSerializerBase
{
public:
using Socket = boost::asio::ip::tcp::socket;
using Handler = boost::asio::any_completion_handler<void (boost::beast::error_code,
std::size_t)>;
virtual ~HttpSerializerBase() = default;
virtual void asyncWriteSome(Socket& tcp, Handler handler) = 0;
virtual bool done() const = 0;
protected:
HttpSerializerBase() = default;
};
//------------------------------------------------------------------------------
template <typename R>
class PolymorphicHttpSerializer : public HttpSerializerBase
{
public:
using Response = R;
template <typename T>
explicit PolymorphicHttpSerializer(T&& response, std::size_t limit)
: response_(std::forward<T>(response)),
serializer_(response_)
{
serializer_.limit(limit);
}
void asyncWriteSome(Socket& tcp, Handler handler) override
{
// A lambda could be used instead in C++14 with move captures
struct Written
{
Handler handler;
void operator()(boost::beast::error_code netEc, std::size_t n)
{
handler(netEc, n);
}
};
boost::beast::http::async_write_some(tcp, serializer_,
Written{std::move(handler)});
}
bool done() const override
{
return serializer_.is_done();
}
private:
using Body = typename Response::body_type;
using Fields = typename Response::fields_type;
using Serializer = boost::beast::http::serializer<false, Body, Fields>;
Response response_;
Serializer serializer_;
};
} // namespace detail
//------------------------------------------------------------------------------
// Type-erases a boost::beast::http::serializer so that the same incremental
// write algorithm can work with any response body type.
//------------------------------------------------------------------------------
class AnyHttpSerializer
{
public:
using Socket = boost::asio::ip::tcp::socket;
using Handler = boost::asio::any_completion_handler<void (boost::beast::error_code,
std::size_t)>;
AnyHttpSerializer() = default;
template <typename R>
AnyHttpSerializer(R&& response, std::size_t limit)
: serializer_(makeSerializer(std::forward<R>(response), limit))
{}
explicit operator bool() const {return empty();}
bool empty() const {return serializer_ == nullptr;}
bool done() const {return empty() ? true : serializer_->done();}
void reset() {serializer_.reset();}
template <typename R>
void reset(R&& response, std::size_t limit)
{
serializer_.reset(makeSerializer(std::forward<R>(response), limit));
}
void asyncWriteSome(Socket& tcp, Handler handler)
{
assert(!empty());
serializer_->asyncWriteSome(tcp, std::move(handler));
}
private:
template <typename R>
static detail::PolymorphicHttpSerializer<typename std::decay<R>::type>*
makeSerializer(R&& response, std::size_t limit)
{
using T = typename std::decay<R>::type;
return new detail::PolymorphicHttpSerializer<T>(std::forward<R>(response),
limit);
}
std::unique_ptr<detail::HttpSerializerBase> serializer_;
}; Usage: class Session : public std::enable_shared_from_this<Session>
{
public:
template <typename R>
void respond(R&& response)
{
serializer_.reset(std::forward<R>(response), incrementalWriteLimit_);
sendMore();
}
private:
void sendMore()
{
auto self = shared_from_this();
serializer_.asyncWriteSome(
socket_,
[this, self](boost::beast::error_code ec, std::size_t n)
{
if (ec)
throw boost::system::system_error{ec};
if (serializer_.done())
onSendComplete();
else
sendMore();
}
}
void onSendComplete()
{
}
AnyHttpSerializer serializer_;
boost::asio::ip::tcp::socket socket_;
std::size_t incrementalWriteLimit_;
} |
Composed operations keep a weak_ptr to the implementation object. The first action in their call operator is an attempt to create a shared_ptr from it. If this fails, the operation complete with beast/include/boost/beast/websocket/impl/read.hpp Lines 85 to 91 in 9d05ef5
This was implemented as part of this commit: Add websocket::stream timeouts. The problem is the read and write buffers owned by the implementation object, if we destroy the implementation object, the ongoing read and write operations will still reference the same memory location.
Therefore, although it is possible to destroy a socket object in Asio at any time, the caller must ensure the validity of the buffer used by all outstanding asynchronous operations until they are completed. |
I don't pretend to understand the internals of Boost.Beast, but it seems to me the best approach upon If I propose that a new What I'm currently doing now when I want Edit: In addition, whatever buffers are referenced by the OS operations are keep alive via the usual |
No I don't think that's a good idea at all. I believe you have the order of operations backwards. You don't destroy the stream and then terminate the operation. You are supposed to terminate the operation and once that is done then destroy the stream. This is how asio sockets work, for the reasons @ashtum gave. |
Sorry, not
|
The way it is now with With Asio sockets, async handlers only need to capture the a If I hope I am making sense. |
Also note that close() for Asio sockets also triggers immediate cancellation:
But, for whatever reason, you chose I can understand why you chose |
Yes that is true about the name of Anyway if I do websockets again I will not be implementing the synchronous interface. |
As I'm using this library, I'm discovering pain points that could be improved upon from a user's perspective. Since this library is now in maintenance mode, and will presumably will not be incorporating any new features, I will add these pain points here instead of creating a new issue every time. If I encounter genuine bugs, I will create an issue.
This is not intended to diminish this excellent library or its author. It is intended to be referenced in future endeavors so that these pain points are at least considered, if not addressed.
1. Parsers, serializers, and buffers are not resettable
In a networking app, these objects should be reusable over and over, instead of constructing them from scratch every time. When storing them as class members, the suggested workaround is to wrap them in
boost::optional
orstd::optional
.2. Closing or destroying a websocket::stream should always be safe.
The websocket::stream destructor documentation has this note:
What Asio does when destroying/closing sockets is that is immediately cancels all pending operations.
The current
websocket::stream::close
method should be renamed to something else that indicates that the operation is not immediate.websocket::stream::close
should have the same immediate-effect semantics as for Asio sockets.3. The state of
next_layer
stream is unknown afterwebsocket::stream::close
completes.I brought this up in #2730. Is the underlying socket still open when
websocket::stream::close
completes? The documentation doesn't say.4. No
websocket::stream::async_wait
methodI brought this up in #2773. I want to receive a notification that data is available to be read, just like in
asio::ip::tcp::socket::async_wait
. As a workaround, I dowebsocket::async_read_some
with a limit of a single byte.5. Incremental HTTP reads are a bit convoluted.
I brought this up in #2797. Incremental HTTP writes are much simpler by comparison and I think incremental reads should be just as easy with a
limit
parameter injected somewhere.6. No type-erased HTTP message wrapper
I can't easily reuse my incremental HTTP write logic (using
http::async_write_some
) with various response types. Some responses havefile_body
, whereas others havestring_body
.There is no
async_write_some
function that takes the type-erasedhttp::message_generator
, so I'm going to have to write my own polymorphic incremental HTTP writer class.7.
websocket::stream
is not move-assignableThis has given me problems in implementing move semantics in the the higher-level objects containing the
websocket::stream
(e.g. transferring the websocket stream from a "listener" object to a "session" object). The workaround I used involved usingboost::optional
and implementing explicit move constructor/assignment operators.8. HTTP incremental writes is clamped to hard-coded 4096 bytes for
file_body
I had configured HTTP incremental writes (using
http::async_write_some
) to use a limit of 8kB, and was puzzled as to why it was only sending 4kB at a time. Stepping with the debugger led me tobasic_file_body::writer::get
, where the size is clamped toBOOST_BEAST_FILE_BUFFER_SIZE
, which is hard-coded to 4096. This can of course be changed at compile-time but it cannot be set at runtime.9. No Range support for
file_body
response<file_body>
cannot be used to send only a sub-range of the file data. Range requests are useful for on-demand video streaming, for example. As a workaround, one can load the requested data range manually from the file, and send it via aresponse<string_body>
orresponse<vector_body>
.The text was updated successfully, but these errors were encountered: