It is possible, but a-typical. As documented, in order to be able to re-run after the service ran out of work (I.e. returning 0 handlers when executed), you need to call restart()
(previously reset()
):
A normal exit from the run()
function implies that the io_context
object is stopped (the stopped()
function returns true
). Subsequent calls to run()
, run_one()
, poll()
or poll_one()
will return immediately unless there is a prior call to restart()
.
Demo
Here is a demo based on your snippet:
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/beast.hpp>
#include <boost/beast/ssl.hpp>
#include <iostream>
#include <optional>
namespace net = boost::asio;
namespace ssl = net::ssl;
namespace beast = boost::beast;
namespace http = beast::http;
using net::ip::tcp;
using namespace std::chrono_literals;
struct X {
std::string host_ = "www.example.com";
std::string port_ = "https";
static auto constexpr kOpTimeout = 3s;
net::io_context io_context_;
ssl::context ctx_{ssl::context::sslv23_client};
std::optional<beast::ssl_stream<beast::tcp_stream>> ssl_stream_{std::in_place,
io_context_, ctx_};
std::optional<tcp::resolver> resolver_{std::in_place, io_context_};
X() { ctx_.set_default_verify_paths(); }
void STAGE1() {
net::spawn(io_context_, [&](net::yield_context yield) {
try {
auto results = resolver_->async_resolve(host_, port_, yield);
ssl_stream_->next_layer().async_connect(results, yield);
ssl_stream_->async_handshake(ssl::stream_base::client, yield);
} catch (std::exception const& e) {
std::cerr << "Whoops in " << __LINE__ << ": " << e.what() << std::endl;
throw;
}
});
if (io_context_.stopped())
io_context_.restart();
try {
io_context_.run();
} catch (std::exception const& e) {
std::cerr << "Whoops in " << __LINE__ << ": " << e.what() << std::endl;
throw;
}
}
auto STAGE2() {
beast::flat_buffer buffer;
http::response<http::dynamic_body> res;
http::request<http::empty_body> beast_request(http::verb::get, "/", 11);
beast_request.set(http::field::host, "www.example.com");
net::spawn(io_context_, [&](net::yield_context yield) {
try {
beast::get_lowest_layer(*ssl_stream_).expires_after(kOpTimeout);
/*auto sent =*/ http::async_write(*ssl_stream_, beast_request, yield);
/*auto read =*/ http::async_read(*ssl_stream_, buffer, res, yield);
} catch (std::exception const& e) {
std::cerr << "Whoops in " << __LINE__ << ": " << e.what() << std::endl;
throw;
}
});
if (io_context_.stopped())
io_context_.restart();
try {
io_context_.run();
} catch (std::exception const& e) {
std::cerr << "Whoops in " << __LINE__ << ": " << e.what() << std::endl;
throw;
}
return res;
}
};
int main() {
X x;
x.STAGE1();
std::cout << x.STAGE2();
}
Prints, locally:
HTTP/1.1 200 OK
Age: 533333
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Sun, 18 Dec 2022 21:50:21 GMT
Etag: "3147526947+ident"
Expires: Sun, 25 Dec 2022 21:50:21 GMT
Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
Server: ECS (bsa/EB20)
Vary: Accept-Encoding
X-Cache: HIT
Content-Length: 1256
<!doctype html>
<html>
<head>
<title>Example Domain</title>
<meta charset="utf-8" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
body {
background-color: #f0f0f2;
margin: 0;
padding: 0;
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
div {
width: 600px;
margin: 5em auto;
padding: 2em;
background-color: #fdfdff;
border-radius: 0.5em;
box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
}
a:link, a:visited {
color: #38488f;
text-decoration: none;
}
@media (max-width: 700px) {
div {
margin: 0 auto;
width: auto;
}
}
</style>
</head>
<body>
<div>
<h1>Example Domain</h1>
<p>This domain is for use in illustrative examples in documents. You may use this
domain in literature without prior coordination or asking for permission.</p>
<p><a href="https://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>
Notes
Again, make buffer
a member, because its context will matter for any subsequent traffic on the connection.
Also, don't use run()
to find out when things are "done". You can use futures, or any other way of synchronization:
Live On Coliru
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/beast.hpp>
#include <boost/beast/ssl.hpp>
#include <iostream>
#include <optional>
namespace net = boost::asio;
namespace ssl = net::ssl;
namespace beast = boost::beast;
namespace http = beast::http;
using net::ip::tcp;
using namespace std::chrono_literals;
using duration = std::chrono::steady_clock::duration;
struct Client {
Client(net::any_io_executor ex) : ssl_stream_{ex, ctx_} {
ctx_.set_default_verify_paths();
}
void connect(std::string host, std::string port = "https") {
host_ = host;
std::packaged_task<void(std::string, std::string, net::yield_context)> task{
[this](std::string host, std::string port, net::yield_context yield) {
tcp::resolver resolver_{ssl_stream_.get_executor()};
auto results = resolver_.async_resolve(host, port, yield);
ssl_stream_.next_layer().async_connect(results, yield);
ssl_stream_.async_handshake(ssl::stream_base::client, yield);
}};
auto fut = task.get_future();
net::spawn(ssl_stream_.get_executor(),
std::bind(std::move(task), std::move(host), std::move(port),
std::placeholders::_1));
return fut.get();
}
using Response = http::response<http::dynamic_body>;
Response makeRequest(std::string target, duration kOpTimeout = 300ms) {
http::request<http::empty_body> beast_request(http::verb::get, target, 11);
beast_request.set(http::field::host, host_);
std::packaged_task<Response(net::yield_context)> task{
[kOpTimeout, req = std::move(beast_request), this](net::yield_context yield) {
Response res;
beast::get_lowest_layer(ssl_stream_).expires_after(kOpTimeout);
/*auto sent =*/http::async_write(ssl_stream_, req, yield);
/*auto read =*/http::async_read(ssl_stream_, buffer_, res, yield);
return res;
}};
auto fut = task.get_future();
net::spawn(ssl_stream_.get_executor(), std::move(task));
return fut.get();
}
private:
ssl::context ctx_{ssl::context::sslv23_client};
beast::ssl_stream<beast::tcp_stream> ssl_stream_;
beast::flat_buffer buffer_;
std::string host_;
};
int main() {
net::thread_pool ioc(1); // 1 thread is enough
Client x(make_strand(ioc)); // strand only required if you have multiple IO threads
x.connect("httpbin.org");
std::cout << x.makeRequest("/delay/2", 3s) << std::endl;
std::cout << "Second request will time out: " << std::endl;
std::cout << x.makeRequest("/delay/5", 1s); // timeout
ioc.join(); // wait for service to complete
}
Locally:
