我正在尝试使用 boost::beast (版本 1.86)和无堆栈协程开发一个快速的 HTTPS 客户端。我正在向 api.mailgun.net 发送 HTTPS 帖子。
一切正常,除了在
async_shutdown
被称为 SSL 167772451 - application data after close notify (SSL routines)
被抛出之后。
我不知道为什么会这样。此时 request_/response_ 指针不应被释放,所有读取都应完整完成。
下面是代码。同样,在最后一个函数(最大的协同例程)中调用
async_shutdown
后,它再次失败。这个错误是“正常”且可以忽略的吗?
/**
* Do the session
*/
struct Session
{
boost::asio::coroutine coroutine_;
boost::shared_ptr<http_ssl_stream> stream_;
boost::shared_ptr<RequestType> request_;
uint timeout_;
std::string host_;
std::string port_;
std::unique_ptr<boost::asio::ip::tcp::resolver> resolver_;
std::unique_ptr<ResponseType> response_;
std::unique_ptr<BufferType> buffer_;
/**
* First call
*/
template<typename Self>
void operator()( Self &self )
{
// Set SNI Hostname (many hosts need this to handshake successfully)
if( !SSL_set_tlsext_host_name(stream_->native_handle(), host_.c_str()) )
{
// Callback with error
return self.complete( boost::beast::error_code( static_cast<int>(::ERR_get_error()), boost::asio::error::get_ssl_category() ), boost::none );
}
// Resolve the resolve
resolver_.reset( new boost::asio::ip::tcp::resolver( stream_->get_executor() ) );
// Resolve
resolver_->async_resolve( host_, port_, std::move( self ) );
}
/**
* On resolved call
*/
template<typename Self>
void operator()( Self &self, boost::beast::error_code error, boost::asio::ip::tcp::resolver::results_type results )
{
// Resolve error, quit
if( error )
{
return self.complete( error, boost::none );
}
// Set the expiration
boost::beast::get_lowest_layer( *stream_ ).expires_after( std::chrono::seconds( timeout_ ) );
// Do a connnect
boost::beast::get_lowest_layer( *stream_ ).async_connect(
results,
std::move( self )
);
}
/**
* On connected
*/
template<typename Self>
void operator()( Self &self, boost::beast::error_code error, boost::asio::ip::tcp::resolver::results_type::endpoint_type results )
{
// Connect error
if( error )
{
return self.complete( error, boost::none );
}
// Set the expiration
boost::beast::get_lowest_layer( *stream_ ).expires_after( std::chrono::seconds( timeout_ ) );
// Do a handshake
stream_->async_handshake(
boost::asio::ssl::stream_base::client,
std::move( self )
);
}
/**
* After handshake
*/
template<typename Self>
void operator()( Self &self, boost::beast::error_code error, std::size_t bytes_transferred=0 )
{
// Coroutine for easy state knowing
BOOST_ASIO_CORO_REENTER( coroutine_ )
{
/*
// Do the handshake
BOOST_ASIO_CORO_YIELD
{
// Connect error
if( error )
return self.complete( error, boost::none );
// Set the expiration
boost::beast::get_lowest_layer( *stream_ ).expires_after( std::chrono::seconds( timeout_ ) );
// Do a handshake
stream_->async_handshake(
boost::asio::ssl::stream_base::client,
std::move( self )
);
}
*/
// Do the write
BOOST_ASIO_CORO_YIELD
{
// Handshake error
if( error )
{
return self.complete( error, boost::none );
}
// Set up an HTTP GET request message
request_->version( 11 );
//request_->body() = body_;
// Set the expiration
boost::beast::get_lowest_layer( *stream_ ).expires_after( std::chrono::seconds( timeout_ ) );
// Write the request
boost::beast::http::async_write( *stream_, *request_, std::move( self ) );
}
// Execute a read
BOOST_ASIO_CORO_YIELD
{
// Write error
if( error )
{
return self.complete( error, boost::none );
}
// Create the response
response_.reset( new ResponseType );
// Create the buffa
buffer_.reset( new BufferType );
// Set the expiration
boost::beast::get_lowest_layer( *stream_ ).expires_after( std::chrono::seconds( timeout_ ) );
// Receive the HTTP response
boost::beast::http::async_read( *stream_, *buffer_, *response_, std::move( self ) );
}
// Shutdown the socket
BOOST_ASIO_CORO_YIELD
{
// Read error
if( error )
{
return self.complete( error, boost::none );
}
// Set the expiration
boost::beast::get_lowest_layer( *stream_ ).expires_after( std::chrono::seconds( timeout_ ) );
// Receive the HTTP response
stream_->async_shutdown( std::move( self ) );
}
// Shutdown error
if( error == boost::asio::error::eof or error == boost::asio::ssl::error::stream_truncated )
{
// Rationale:
// http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error
error = {};
}
// Did we get it?
if( error )
{
self.complete( error, boost::none );
return;
}
// Return no error and the buffer
self.complete( error, *response_ );
}
}
};
我在我这边复制了它,并使用 GET 到
google.com
。我得到了 stream_truncated
错误 正如在野外所预料的那样。你已经处理过这个问题了:
用
www.google.com
替换 api.mailgun.net
只会消除我的症状:
您可以比较您的实现并与您自己的测试进行检查吗?
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/beast.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/url.hpp>
#include <iostream>
namespace beast = boost::beast;
namespace http = boost::beast::http;
namespace net = boost::asio;
namespace ssl = net::ssl;
using url = boost::urls::url;
using tcp = net::ip::tcp;
using http_ssl_stream = boost::beast::ssl_stream<boost::beast::tcp_stream>;
using RequestType = http::request<http::string_body>;
using ResponseType = http::response<http::string_body>;
using BufferType = boost::beast::flat_buffer;
/**
* Do the session
*/
struct Session {
net::coroutine coroutine_; // by value
boost::shared_ptr<http_ssl_stream> stream_;
boost::shared_ptr<RequestType> request_;
uint timeout_; // by value
std::string host_, port_; // ephemeral
std::unique_ptr<tcp::resolver> resolver_ = {}; // stable
std::unique_ptr<ResponseType> response_ = {}; // stable
std::unique_ptr<BufferType> buffer_ = {}; // stable
/**
* First call
*/
template <typename Self> void operator()(Self& self) {
std::cout << __LINE__ << ": " << std::endl;
// Set SNI Hostname (many hosts need this to handshake successfully)
if (!SSL_set_tlsext_host_name(stream_->native_handle(), host_.c_str())) {
// Callback with error
return self.complete(
beast::error_code(static_cast<int>(::ERR_get_error()), net::error::get_ssl_category()),
boost::none);
}
resolver_ = std::make_unique<tcp::resolver>(stream_->get_executor());
resolver_->async_resolve(host_, port_, std::move(self));
}
/**
* On resolved call
*/
template <typename Self>
void operator()(Self& self, beast::error_code error, tcp::resolver::results_type results) {
std::cout << __LINE__ << ": " << error.message() << std::endl;
// Resolve error, quit
if (error) {
return self.complete(error, boost::none);
}
// Set the expiration
beast::get_lowest_layer(*stream_).expires_after(std::chrono::seconds(timeout_));
// Do a connnect
beast::get_lowest_layer(*stream_).async_connect(results, std::move(self));
}
/**
* On connected
*/
template <typename Self>
void operator()(Self& self, beast::error_code error,
tcp::resolver::results_type::endpoint_type /*results*/) {
std::cout << __LINE__ << ": " << error.message() << std::endl;
// Connect error
if (error) {
return self.complete(error, boost::none);
}
// Set the expiration
beast::get_lowest_layer(*stream_).expires_after(std::chrono::seconds(timeout_));
// Do a handshake
stream_->async_handshake(ssl::stream_base::client, std::move(self));
}
/**
* After handshake
*/
template <typename Self>
void operator()(Self& self, beast::error_code error, size_t /*bytes_transferred*/ = 0) {
// Coroutine for easy state knowing
BOOST_ASIO_CORO_REENTER(coroutine_) {
/*
// Do the handshake
BOOST_ASIO_CORO_YIELD
{
// Connect error
if( error )
return self.complete( error, boost::none );
// Set the expiration
beast::get_lowest_layer( *stream_ ).expires_after( std::chrono::seconds( timeout_ ) );
// Do a handshake
stream_->async_handshake(
ssl::stream_base::client,
std::move( self )
);
}
*/
std::cout << __LINE__ << ": " << error.message() << std::endl;
// Do the write
BOOST_ASIO_CORO_YIELD {
// Handshake error
if (error) {
return self.complete(error, boost::none);
}
// Set up an HTTP GET request message
request_->version(11);
// request_->body() = body_;
// Set the expiration
beast::get_lowest_layer(*stream_).expires_after(std::chrono::seconds(timeout_));
// Write the request
http::async_write(*stream_, *request_, std::move(self));
}
std::cout << __LINE__ << ": " << error.message() << std::endl;
// Execute a read
BOOST_ASIO_CORO_YIELD {
// Write error
if (error) {
return self.complete(error, boost::none);
}
// Create the response
response_ = std::make_unique<ResponseType>();
// Create the buffa
buffer_ = std::make_unique<BufferType>();
// Set the expiration
beast::get_lowest_layer(*stream_).expires_after(std::chrono::seconds(timeout_));
// Receive the HTTP response
http::async_read(*stream_, *buffer_, *response_, std::move(self));
}
std::cout << __LINE__ << ": " << error.message() << std::endl;
// Shutdown the socket
BOOST_ASIO_CORO_YIELD {
// Read error
if (error) {
return self.complete(error, boost::none);
}
// Set the expiration
beast::get_lowest_layer(*stream_).expires_after(std::chrono::seconds(timeout_));
// Receive the HTTP response
stream_->async_shutdown(std::move(self));
}
std::cout << __LINE__ << ": " << error.message() << std::endl;
// Shutdown error
if (error == net::error::eof or error == ssl::error::stream_truncated) {
// Rationale:
// http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error
error = {};
}
std::cout << __LINE__ << ": " << error.message() << std::endl;
// Did we get it?
if (error) {
self.complete(error, boost::none);
return;
}
// Return no error and the buffer
self.complete(error, *response_);
}
}
};
template <typename Executor, typename Token> auto async_https_get(Executor ex, url what, Token&& token) {
static ssl::context ctx(ssl::context::tlsv12_client);
std::string port = what.has_port() ? what.port() : "https";
std::string resource = what.encoded_resource().decode();
std::cout << "Host: " << what.host() << " Port: " << port << " Resource: " << resource << std::endl;
auto request = boost::make_shared<RequestType>(http::verb::get, resource, 11, "{}");
request->set(http::field::host, what.host());
Session session{
net::coroutine(),
boost::make_shared<http_ssl_stream>(ex, ctx),
request,
5, // seconds
what.host(),
port,
};
return net::async_compose<Token, void(beast::error_code, boost::optional<ResponseType>)>(
std::move(session), token, ex);
}
int main() {
net::thread_pool ioc(1);
// url what("https://www.google.com/");
url what("https://api.mailgun.net/");
async_https_get(make_strand(ioc), what,
[&](beast::error_code ec, boost::optional<ResponseType> response) {
std::cout << "--\nCompleting with error: " << ec.message() << std::endl;
if (!ec)
std::cout << "--\nResponse: " << response->base() << std::endl;
});
ioc.join();
}