I'm implementing a client which accesses a REST endpoint and then begins processing an SSE stream and monitoring events as they occur. To this end, I'm using Boost::Beast version 124 with Boost 1.63 and attempting to use async_read_some to incrementally read the body of the response.
Here's my code thus far:
namespace http = boost::beast::http;
http::response_parser<http::string_body> sse_client::m_parser;
http::response<http::string_body> sse_client::m_response;
boost::beast::flat_buffer m_buffer;
void sse_client::monitor_sse()
{
http::request<http::empty_body> req{http::verb::get, m_target, 11};
req.set(http::field::host, m_host);
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
req.set(http::field::accept, "text/event-stream");
http::async_write(m_socket, req,
std::bind(
&sse_client::process_sse,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
void sse_client::process_sse(boost::system::error_code ec, std::size_t byte_count)
{
http::read_header(m_socket, m_buffer, m_parser);
http::async_read_some(m_socket, m_buffer, m_parser,
std::bind(
&sse_client::read_event,
shared_from_this(),
std::placeholders::_1));
}
void sse_client::read_event(boost::system::error_code ec)
{
// TODO: process event
http::async_read_some(m_socket, m_buffer, m_parser,
std::bind(
&sse_client::read_event,
shared_from_this(),
std::placeholders::_1));
}
My questions are:
Is this the right approach for this particular use case?
Is there a more appropriate type to use with response_parser and response than http::string_body?
When the read_event handler is invoked, how does it access the content retrieved by async_read_some? Should it be pulled from the buffer?
I'll answer your questions first and then provide explanation.
Yes, you want to read the header and then call read_some (or read, see below) until the parser returns true from is_complete(). However, in your code I notice you are mixing synchronous and asynchronous calls (read_header followed by async_read_some). It would be best to stick to just one model instead of mixing them.
For your purposes you probably want buffer_body instead of string_body. There is an example in the documentation which shows how to do this (http://www.boost.org/doc/libs/1_66_0/libs/beast/doc/html/beast/using_http/parser_stream_operations/incremental_read.html)
The "buffer" you refer to is the dynamic buffer argument passed to the HTTP stream operation. While this buffer will hold the message data, it is not for the application to inspect. This buffer is used to hold additional data past the end of the current message that the stream algorithm can read (this is explained in http://www.boost.org/doc/libs/1_66_0/libs/beast/doc/html/beast/using_http/message_stream_operations.html#beast.using_http.message_stream_operations.reading). You will access the content by inspecting the body of the message when using buffer_body
http::response_parser::get() will provide you with access to the message being read in.
The best solution for you is to use buffer_body as in the example, provide an area of memory to point it to and then call read or async_read in a loop. Every time the buffer is full, the read will return with the error beast::http::error::need_buffer, indicating that further calls are required.
Hope this helps!
Related
My goal is to register to a websocket service to get real-time company quotations.
So I based my code on the following example, by mostly calling (again) async_read, once we receive a quotation to accept futures quotation:
https://www.boost.org/doc/libs/master/libs/beast/example/websocket/client/async-ssl/websocket_client_async_ssl.cpp
The problem is when I am waiting for a new quotation (who could take sometimes minutes or hours for small companies), the program is blocked waiting for a message and I do not have the opportunity to ask for another company.
I tried to use the "post" function to call again async_write in the good context thread but the program crashed.
Is there any way to force the completion of callback on_read, to have then the opportunity to send a new message?
Here is the function I modified (simplified without mutexes):
void
on_read(
beast::error_code ec,
std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred);
if(ec)
return fail2(ec, "read");
std::string mycontent = beast::buffers_to_string(buffer_.data());
cout << mycontent << endl;
buffer_.clear();
ws_.async_read(
buffer_,
beast::bind_front_handler(
&session::on_read,
shared_from_this()));
}
void subscribe(const std::string &symbol)
{
// We save the message in the queue
std::string text = "{\"action\": \"subscribe\", \"symbols\": \"" + symbol + "\"}";
msgqueue_.push_back(text);
boost::asio::post(ioc_, beast::bind_front_handler(&session::_subscription_to_post, shared_from_this()));
}
void _subscription_to_post()
{
if (msgqueue_.empty())
return;
// We send the message
ws_.async_write(
net::buffer(msgqueue_.front()),
beast::bind_front_handler(
&session::on_write,
shared_from_this()));
msgqueue_.pop_front();
}
And the program crashes immediately when trying to async_write.
The problem is when I am waiting for a new quotation [...] the program is blocked waiting for a message
It isn't technically blocked because you are using async_read
I tried to use the "post" function to call again async_write in the good context thread, but the program crashed.
That means you're doing something wrong. You can post a question with your self-contained minimal code, and we can tell you what is wrong.
In general, you can use a single read operation and a single write operation concurrently (as in: in flight, asynchronously, you still need to synchronize threads accessing all related resources).
Typically, you have a single async-read-chain active at all times, and a outbound message-queue that is drained by a single async-write chain (that obviously ends when the queue is empty, so needs to be initiated when the first outbound message is queued).
I have many answers on this site (literally dozens) that you may be able to find by search for outbox or outbox_ e.g. Keep in mind that the majority of them will deal with plain (ssl) sockets intead of websockets, but the pattern practically the same.
Is there any way to force the completion of callback on_read, to have then the opportunity to send a new message ?
You can technically cancel() it, which completes it with operation_aborted. But that is not what you need. You want full-duplex, so writing cancel() is the opposite of what you want.
We have boost asio ssl server that reads data from a client. We have a requirement to perform actual read of the data in our own code as oppose to read directly by boost async_read_some() routines by passing a buffer to it. Hence we pass null_buffers() to async_read_some() and later do the actual data read using socket->read_some() API. Our sockets are all always non-blocking for read and write. http://www.boost.org/doc/libs/1_40_0/doc/html/boost_asio/overview/core/reactor.html
This works fine with normal stream (tcp) socket. However, with ssl socket, it is not working correctly.
read callback constantly gets called even when there is no data available. This churns the cpu for ever.. When ssl_socket->read_some() is called, we get 0 bytes. error code is set to 11 (Resource unavailable)
In the read callback, we always trigger future reads again, by calling socket->async_read_some(null_buffers(), ...)
If we don't do (2), then not all data is received, when large amounts of data needs to be read. If we do (2), all data is correctly received (over many read callbacks), but the read callback keeps getting called even after all data has been read and when there is no more data to read...
To verify the same, I used the example boost ssl server and client code and notice a similar behavior with following change to the server code
http://www.boost.org/doc/libs/1_40_0/doc/html/boost_asio/example/ssl/server.cpp
handle_read() call back constantly gets called for ever... If we use normal boost stream (tcp) socket (not boost ssl), then we do not see such callbacks when no data is available to read.
We see the same behavior even with the latest boost and ssl code as well. Is null_buffers() read/write mode with ssl socket well tested and supported ? I could not find much documentation for this any where..
Can some one please help. Thank you so much!
diff --git a/server.cc b/server.cc
index 3f2b028..bfd65c7 100644
--- a/server.cc
+++ b/server.cc
## -40,7 +40,10 ## public:
{
if (!error)
{
- socket_.async_read_some(boost::asio::buffer(data_, max_length),
+ socket().non_blocking(true);
+
+ // socket_.async_read_some(boost::asio::buffer(data_, max_length),
+ socket_.async_read_some(boost::asio::null_buffers(),
boost::bind(&session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
## -56,6 +59,8 ## public:
{
if (!error)
{
+ boost::system::error_code err;
+ bytes_transferred = socket_.read_some(boost::asio::mutable_buffers_1(boost::asio::buffer(data_, max_length)), err);
boost::asio::async_write(socket_,
boost::asio::buffer(data_, bytes_transferred),
boost::bind(&session::handle_write, this,
boost::asio::placeholders::error));
+
+ // Post for read again..
+ socket_.async_read_some(boost::asio::null_buffers(),
+ boost::bind(&session::handle_read, this,
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
I've written a program which uses boost:asio library, which transfers data between 2 tcp servers.
One server uses the following code to send data:
std::shared_ptr<std::string> s =
std::make_shared<std::string>(message);
boost::asio::async_write(socket_, boost::asio::buffer(*s),
std::bind(&TcpServer::HandleWrite, shared_from_this(), s, _1, _2));
In another TcpServer, when I use async_read_until to get data, everything works fine, but if I replace async_read_until to async_read, it gives an End Of File error:
boost::asio::streambuf input_buffer;
boost::asio::async_read_until(socket_, input_buffer, match_condition(),
std::bind(&TcpServer::HandleRead, shared_from_this(), _1));
//boost::asio::async_read(socket_, input_buffer, boost::asio::transfer_all(),
// std::bind(&TcpServer::HandleRead, shared_from_this(), _1));
If I use boost::asio::transfer_at_least(1) in async_read, I can get the expected result.
Why did this happen?
An eof error indicates that the writer side closed the connection. Any data sent before that should still be available in the TcpServer::HandleRead callback. Check the bytes_transferred parameter to find out how much data was received by the reader.
I am trying to use boost asio library to implement network programming.
Here is the code which is called when some data(which ends with "##") arrives at the endpoint.
{
boost::asio::async_read_until(m_socket, m_response,
std::string("##"),
boost::bind(&CTcpClient::HandleReceive,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void CTcpClient::HandleReceive(const ErrorCodeType p_errorCode, size_t p_length)
{
IN_FUNCTION
if ( !p_errorCode )
{
logInfo(STR("Data received ..."));
boost::asio::async_read_until(m_socket, m_response,
std::string("##"),
boost::bind(&CTcpClient::HandleReceive,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
m_onReceiveSignal(sbuf2s(m_response));
}
else
{
Shutdown(p_errorCode);
}
OUT_FUNCTION
}
Let's say, the data that is sent to the end point is "KINGS##". Hence, the Handlereceive should be called once. But in my code, this is being called twice, once with "KINGS##" and another with a null string.
Can somebody tell me what is the reason and how to fix it ?
You need to be clearing out your m_response buffer, up to the token, after a successful read.
Because you aren't doing this before re-issuing an async read, your response buffer still has the ## characters in it, so the read is going to complete immediately.
As a side note, the async_read_until call may read data beyond the ##, so you have to be careful to only clear up to and including the ##, but not past it.
I think you just need to call async_read_until(...) after m_onReceiveSignal(...).
Change the order of both inside Handlereceive.
That is under the assumption the first time you call the Handlereceive is from another method (connect maybe) through async_read_untill(...)
I just started working with the Boost ASIO library, version 1.52.0. I am using TCP/SSL encryption with async sockets. From other questions asked here about ASIO, it seems that ASIO does not support receiving a variable length message and then passing the data for that message to a handler.
I'm guessing that ASIO puts the data into a cyclical buffer and loses all track of each separate message. If I have missed something and ASIO does provide a way to pass individual messages, then please advise as to how.
My question is that assuming I can't somehow obtain just the bytes associated with an individual message, can I use transfer_exactly in async_read to obtain just the first 4 bytes, which our protocol always places the length of the message. Then call either read or async_read (if read won't work with async sockets) to read in the rest of the message? Will this work? Any better ways to do it?
Typically I like to take the data I receive in an async_read and put it in a boost::circular_buffer and then let my message parser layer decide when a message is complete and pull the data out.
http://www.boost.org/doc/libs/1_52_0/libs/circular_buffer/doc/circular_buffer.html
Partial code snippets below
boost::circular_buffer TCPSessionThread::m_CircularBuffer(BUFFSIZE);
void TCPSessionThread::handle_read(const boost::system::error_code& e, std::size_t bytes_transferred)
{
// ignore aborts - they are a result of our actions like stopping
if (e == boost::asio::error::operation_aborted)
return;
if (e == boost::asio::error::eof)
{
m_SerialPort.close();
m_IoService.stop();
return;
}
// if there is not room in the circular buffer to hold the new data then warn of overflow error
if (m_CircularBuffer.reserve() < bytes)
{
ERROR_OCCURRED("Buffer Overflow");
m_CircularBuffer.clear();
}
// now place the new data in the circular buffer (overwrite old data if needed)
// note: that if data copying is too expensive you could read directly into
// the circular buffer with a little extra effor
m_CircularBuffer.insert(m_CircularBuffer.end(), pData, pData + bytes);
boost::shared_ptr<MessageParser> pParser = m_pParser.lock(); // lock the weak pointer
if ((pParser) && (bytes_transferred))
pParser->HandleInboundPacket(m_CircularBuffer); // takes a reference so that the parser can consume data from the circ buf
// start the next read
m_Socket.async_read_some(boost::asio::buffer(*m_pBuffer), boost::bind(&TCPSessionThread::handle_read, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}