Boost Beast无栈协程HTTPS客户端调用async_shutdown后抛SSL错误
问题
我正在用boost::beast 1.86版本结合无栈协程开发简易HTTPS客户端,向api.mailgun.net发送HTTPS POST请求。所有功能都正常,但调用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_ ); } } };
解答
这个错误属于正常情况,完全可以忽略,具体原因和处理方式如下:
- 错误本质:该错误表示SSL关闭通知(close notify)发送后,客户端又收到了服务器传来的应用层数据。很多服务器(包括api.mailgun.net)在完成HTTP响应发送后,会直接终止TCP连接,不会严格遵循SSL双向关闭流程——也就是不等客户端发送close notify就直接断开,导致客户端执行
async_shutdown时触发这个错误。 - 业务影响:你已经完成了所有HTTP请求与响应的交互,核心业务逻辑已经正常执行完毕,这个错误只是SSL层的“收尾流程不规范”,不会影响功能正确性。
- 代码优化:你当前已经处理了
eof和stream_truncated错误,只需将这个新错误也加入忽略列表即可,对应的Boost错误码是boost::asio::ssl::error::application_data_after_close_notify,修改后的判断代码如下:
if( error == boost::asio::error::eof || error == boost::asio::ssl::error::stream_truncated || error == boost::asio::ssl::error::application_data_after_close_notify ) { error = {}; }
内容的提问来源于stack exchange,提问作者Mikey A. Leonetti
相关产品推荐
相关产品推荐

