I had a synchronous method that send https request using http::write
and than expect to read it's response using http::read
.
However, in order to add timeout I had to move to async calls in my method. So I've tried to use http::async_read
and http::async_write
, but keep this overall flow synchronous so the method will return only once it has the https response.
here's my attempt :
class httpsClass {
std::optional<boost::beast::ssl_stream<boost::beast::tcp_stream>> ssl_stream_;
httpsClass(..) {
// notice that ssl_stream_ is initialized according to io_context_/ctx_
// that are class members that get set by c'tor args
ssl_stream_.emplace(io_context_, ctx_);
}
}
std::optional<boost::beast::http::response<boost::beast::http::dynamic_body>>
httpsClass::sendHttpsRequestAndGetResponse (
const boost::beast::http::request<boost::beast::http::string_body>
&request) {
try{
boost::asio::io_context ioc;
beast::flat_buffer buffer;
http::response<http::dynamic_body> res;
beast::get_lowest_layer(*ssl_stream_).expires_after(kTimeout);
boost::asio::spawn(ioc, [&, this](boost::asio::yield_context yield) {
auto sent = http::async_write(this->ssl_stream_.value(), request, yield);
auto received = http::async_read(this->ssl_stream_.value(), buffer, res, yield);
});
ioc.run();// this will finish only once the task above will be fully executed.
return res;
} catch (const std::exception &e) {
log("Error sending/receiving:{}", e.what());
return std::nullopt;
}
}
During trial, this method above reaches the task I assign for the internal io contexts (ioc). However, it gets stuck inside this task on the method async_write.
Anybody can help me figure out why it gets stuck? could it be related to the fact that ssl_stream_ is initialize with another io context object (io_context_) ?
Yes. The default executor for completion handlers on the ssl_stream_ is the outer io_context, which cannot make progress, because you're likely not running it.
My hint would be to:
future<Response>
rather thanoptional<Response>
(which loses the the error information)io_context&
. Instead pass executors, which you can more easily change to be astrand
executor if so required.Adding some code to make it self-contained:
Your implementation was pretty close, except for the unnecessary service:
Now, it is important to have the
io_service
run()
-ning for any asynchronous operations. With completely asynchronous code you wouldn't need threads, but as you are blocking on the response you will. The easiest way is to replaceio_service
with athread_pool
which does therun()
-ning for you.As you can see this test will run two requests against https://httpbin.org/#/Dynamic_data/get_delay__delay_. The second will timeout because 5s exceeds the 3s expiration on the ssl_stream_.
Full Demo
Live On Coliru
Live on my system: