I'm trying to develop a quick HTTPS client using boost::beast (version 1.86) and stackless coroutines. I'm throwing up an HTTPS post to api.mailgun.net.
Everything works EXCEPT RIGHT AFTER the async_shutdown
is called an SSL 167772451 - application data after close notify (SSL routines)
is thrown.
I'm not sure why that is. The request_/response_ pointers should not be freed at this point and all of the reading should be completed in its entirety.
Below is the code. Again, it fails right after async_shutdown
is called in the last function which is the biggest co-routine. Is this error "normal" and ignorable?
/*** 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_ ); } }};