c++ 使用boost/beast发送异步https请求

ryevplcw  于 2022-12-15  发布在  其他
关注(0)|答案(1)|浏览(423)

我有一个同步方法,它使用http::write发送https请求,然后使用http::read读取它的响应。
然而,为了增加超时,我不得不在我的方法中转移到异步调用,所以我尝试使用http::async_readhttp::async_write,但是保持整个流程同步,这样方法只有在得到https响应时才会返回。
下面是我尝试:

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;
  }
}

在试验过程中,上面的方法到达了我为内部io上下文(ioc)分配的任务,但是,它在async_write方法上卡在了这个任务中。
有人能帮我弄清楚为什么它卡住了吗?它会不会和ssl_stream_ is用另一个io上下文对象(io_context_)初始化的事实有关?

lqfhib0f

lqfhib0f1#

ssl_stream_上的完成处理程序的默认执行器是外部io_context,它无法执行,因为您可能没有运行它。
我的建议是:

  • 避免创建第二个io_context
  • 也使用更典型的future<Response>,而不是optional<Response>(丢失错误信息)
  • 避免传递io_context&,而是传递执行器,如果需要,可以更容易地将其更改为strand执行器。

添加一些代码使其自包含:

class httpsClass {
    ssl::context&                                       ctx_;
    std::string                                         host_;
    std::optional<beast::ssl_stream<beast::tcp_stream>> ssl_stream_;
    beast::flat_buffer                                  buffer_;

    static constexpr auto kTimeout = 3s;

  public:
    httpsClass(net::any_io_executor ex, ssl::context& ctx, std::string host)
        : ctx_(ctx)
        , host_(host)
        , ssl_stream_(std::in_place, ex, ctx_) {

        auto ep = tcp::resolver(ex).resolve(host, "https");
        ssl_stream_->next_layer().connect(ep);
        ssl_stream_->handshake(ssl::stream_base::handshake_type::client);
        log("Successfully connected to {} for {}",
            ssl_stream_->next_layer().socket().remote_endpoint(), ep->host_name());
    }

    using Request  = http::request<http::string_body>;
    using Response = http::response<http::dynamic_body>;

    std::future<Response> performRequest(Request const&);
};

您的实现非常接近,除了不必要的服务:

std::future<httpsClass::Response>
httpsClass::performRequest(Request const& request) {
    std::promise<Response> promise;
    auto fut = promise.get_future();

    auto coro = [this, r = request, p = std::move(promise)] //
        (net::yield_context yield) mutable {
            try {
                auto& s = *ssl_stream_;
                get_lowest_layer(s).expires_after(kTimeout);

                r.prepare_payload();
                r.set(http::field::host, host_);

                auto sent = http::async_write(s, r, yield);
                log("Sent: {}", sent);

                http::response<http::dynamic_body> res;
                auto received = http::async_read(s, buffer_, res, yield);
                log("Received: {}", received);
                p.set_value(std::move(res));
            } catch (...) {
                p.set_exception(std::current_exception());
            }
        };

    spawn(ssl_stream_->get_executor(), std::move(coro));
    return fut;
}

现在,对于任何异步操作来说,io_servicerun()-ning都是很重要的。对于完全异步的代码,你不需要线程,但是当你阻塞响应时,你会需要线程。最简单的方法是用thread_pool代替io_servicethread_pool会为你做run()-ning。

int main() {
    net::thread_pool ioc;
    ssl::context ctx(ssl::context::sslv23_client);
    ctx.set_default_verify_paths();

    for (auto query : {"/delay/2", "/delay/5"}) {
        try {
            httpsClass client(make_strand(ioc), ctx, "httpbin.org");

            auto res = client.performRequest({http::verb::get, query, 11});

            log("Request submitted... waiting for response");
            log("Response: {}", res.get());
        } catch (boost::system::system_error const& se) {
            auto const& ec = se.code();
            log("Error sending/receiving:{} at {}", ec.message(), ec.location());
        } catch (std::exception const& e) {
            log("Error sending/receiving:{}", e.what());
        }
    }

    ioc.join();
}

如您所见,此测试将针对www.example.com运行两个请求https://httpbin.org/#/Dynamic_data/get_delay__delay_。第二个请求将超时,因为5s超过了ssl_stream_上的3s过期时间。

完整演示

**一个

#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/beast.hpp>
#include <boost/beast/ssl.hpp>
#include <fmt/ostream.h>
#include <fmt/ranges.h>
#include <optional>
using namespace std::chrono_literals;
namespace net   = boost::asio;
namespace beast = boost::beast;
namespace http  = beast::http;
namespace ssl   = net::ssl;
using net::ip::tcp;

////// LOG STUBS
template <> struct fmt::formatter<boost::source_location> : fmt::ostream_formatter {};
template <> struct fmt::formatter<tcp::endpoint> : fmt::ostream_formatter {};
template <bool isRequest, typename... Args>
struct fmt::formatter<http::message<isRequest, Args...>> : fmt::ostream_formatter {};

static inline void log(auto const& fmt, auto const&... args) {
    fmt::print(fmt::runtime(fmt), args...);
    fmt::print("\n");
    std::fflush(stdout);
}
////// END LOG STUBS

class httpsClass {
    ssl::context&                                       ctx_;
    std::string                                         host_;
    std::optional<beast::ssl_stream<beast::tcp_stream>> ssl_stream_;
    beast::flat_buffer                                  buffer_;

    static constexpr auto kTimeout = 3s;

  public:
    httpsClass(net::any_io_executor ex, ssl::context& ctx, std::string host)
        : ctx_(ctx)
        , host_(host)
        , ssl_stream_(std::in_place, ex, ctx_) {

        auto ep = tcp::resolver(ex).resolve(host, "https");
        ssl_stream_->next_layer().connect(ep);
        ssl_stream_->handshake(ssl::stream_base::handshake_type::client);
        log("Successfully connected to {} for {}",
            ssl_stream_->next_layer().socket().remote_endpoint(), ep->host_name());
    }

    using Request  = http::request<http::string_body>;
    using Response = http::response<http::dynamic_body>;

    std::future<Response> performRequest(Request const&);
};

std::future<httpsClass::Response>
httpsClass::performRequest(Request const& request) {
    std::promise<Response> promise;
    auto fut = promise.get_future();

    auto coro = [this, r = request, p = std::move(promise)] //
        (net::yield_context yield) mutable {
            try {
                auto& s = *ssl_stream_;
                get_lowest_layer(s).expires_after(kTimeout);

                r.prepare_payload();
                r.set(http::field::host, host_);

                auto sent = http::async_write(s, r, yield);
                log("Sent: {}", sent);

                http::response<http::dynamic_body> res;
                auto received = http::async_read(s, buffer_, res, yield);
                log("Received: {}", received);
                p.set_value(std::move(res));
            } catch (...) {
                p.set_exception(std::current_exception());
            }
        };

    spawn(ssl_stream_->get_executor(), std::move(coro));
    return fut;
}

int main() {
    net::thread_pool ioc;
    ssl::context ctx(ssl::context::sslv23_client);
    ctx.set_default_verify_paths();

    for (auto query : {"/delay/2", "/delay/5"}) {
        try {
            httpsClass client(make_strand(ioc), ctx, "httpbin.org");

            auto res = client.performRequest({http::verb::get, query, 11});

            log("Request submitted... waiting for response");
            log("Response: {}", res.get());
        } catch (boost::system::system_error const& se) {
            auto const& ec = se.code();
            log("Error sending/receiving:{} at {}", ec.message(), ec.location());
        } catch (std::exception const& e) {
            log("Error sending/receiving:{}", e.what());
        }
    }

    ioc.join();
}

在我的系统上运行:

相关问题