You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 04:25:58