Skip to content

Instantly share code, notes, and snippets.

@beached
Created July 12, 2018 04:17
Show Gist options
  • Save beached/ddf9205a87dabc0cbda6212824cca2bd to your computer and use it in GitHub Desktop.
Save beached/ddf9205a87dabc0cbda6212824cca2bd to your computer and use it in GitHub Desktop.
// The MIT License (MIT)
//
// Copyright (c) 2018 Darrell Wright
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files( the "Software" ), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include <array>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/asio/ssl/error.hpp>
//#include <boost/asio/detail/string_view.hpp>
#include <cassert>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
class network_client {
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> m_socket;
public:
template<typename OnConnectedCallback, typename VerifySignatureCallback>
network_client( boost::asio::io_service &io_service, boost::asio::ssl::context &context, std::string_view host,
uint16_t port, OnConnectedCallback &&connected_cb, VerifySignatureCallback &&verify_cb )
: m_socket( io_service, context ) {
// static_assert( std::is_invocable_v<OnConnectedCallback, network_client &, boost::system::error_code>,
// "OnConnectedCallback must be invocable with an error_code argument" );
// static_assert(
// std::is_invocable_r_v<bool, VerifySignatureCallback, bool, boost::asio::ssl::verify_context>,
// "VerifySignatureCallback must return a bool and be invocable with bool and verify_context arguments" );
assert( !host.empty( ) );
assert( port > 0 );
auto resolver = boost::asio::ip::tcp::resolver( io_service );
auto query = boost::asio::ip::tcp::resolver::query( std::string( host ), std::to_string( port ) );
auto resolved_it = resolver.resolve( query );
m_socket.set_verify_mode( boost::asio::ssl::verify_peer );
// Setup callback for verifying certificate
m_socket.set_verify_callback(
[verify_cb = std::forward<VerifySignatureCallback>( verify_cb )]( bool preverified, auto &&ssl_context ) mutable {
return verify_cb( preverified, std::forward<decltype( ssl_context )>( ssl_context ) );
} );
// Setup callback for connect
boost::asio::async_connect(
m_socket.lowest_layer( ), resolved_it,
[self = this, connected_cb = std::forward<OnConnectedCallback>( connected_cb )](
boost::system::error_code const &ec, boost::asio::ip::tcp::endpoint const &ep ) mutable {
// TCP Connection Established
if( ec ) {
// We have an error in connecting, call connected_cb and do not attempt TLS
// connection
std::cerr << "Error connecting to " << ep << ": " << ec.message( ) << '\n';
connected_cb( *self, ec );
return;
}
std::cout << "Connected to " << ep << '\n';
self->m_socket.async_handshake( boost::asio::ssl::stream_base::client,
[self, connected_cb = std::forward<OnConnectedCallback>( connected_cb )](
boost::system::error_code const &err_c ) mutable {
// TLS Connection Established
connected_cb( *self, err_c );
} );
} );
}
template<typename Container, typename OnWriteCompleted>
size_t async_write( Container &&c, OnWriteCompleted &&on_write_completed ) {
// Container must have data( ) member and size( ) like a vector
// Not reentrant, make atomic if this is needed
static intmax_t message_id = 0;
intmax_t current_message_id = message_id++;
// static_assert(
// std::is_invocable_v<OnWriteCompleted, network_client &, boost::system::error_code, size_t, size_t,
// Container>, "OnWriteCompleted must accept an network_client &, error_code, size_t/*bytes transferred*/, "
// "size_t/*message_id*/, Container" );
// Buffer needs to live as long as callback and the handler must be move constructable, using unique_ptr
auto buff = std::make_unique<Container>( std::forward<Container>( c ) );
static constexpr size_t const data_member_size = sizeof( *c.data( ) );
size_t const data_size = data_member_size * c.size( );
// must be mutable as on_write_completed's operator( ) constness cannot be controlled
boost::asio::async_write( m_socket, boost::asio::buffer( static_cast<char const *>( buff->data( ) ), data_size ),
[self = this, buff = std::move( buff ), current_message_id,
on_write_completed = std::forward<OnWriteCompleted>( on_write_completed )](
boost::system::error_code const &ec, size_t bytes_transferred ) mutable {
// This will be called when there is either an error or the write completes. The
// bytes_transferred will be the same as the the buffer size on succes, otherwise it
// will indicate how many bytes did transfer so that recovery may be possible.
on_write_completed( *self, ec, bytes_transferred, current_message_id,
std::move( *buff ) );
} );
return message_id;
}
template<typename OnReadCompletedCallback>
void async_read( OnReadCompletedCallback &&on_read_completed ) {
// static_assert(
// std::is_invocable_v<OnReadCompletedCallback, network_client &, std::string_view, boost::system::error_code>
//);
auto read_buffer = std::make_unique<std::array<char, 1024>>( );
std::fill( read_buffer->begin( ), read_buffer->end( ), 0 );
auto d = read_buffer->data( );
size_t s = read_buffer->size( );
// must use mutable because we cannot control the constness of the callback's operator( )
boost::asio::async_read( m_socket, boost::asio::buffer( d, s ),
[self = this, read_buffer = std::move( read_buffer ),
on_read_completed = std::forward<OnReadCompletedCallback>( on_read_completed )](
boost::system::error_code const &ec, size_t bytes_transferred ) mutable {
on_read_completed( *self, std::string_view( read_buffer->data( ), bytes_transferred ),
ec );
} );
}
template<typename OnReadCompletedCallback>
void async_read_until( std::string_view sentinal, OnReadCompletedCallback &&on_read_completed ) {
// static_assert(
// std::is_invocable_v<OnReadCompletedCallback, network_client &, std::string_view, boost::system::error_code>
//);
auto read_buffer = std::make_unique<boost::asio::streambuf>( );
// must use mutable because we cannot control the constness of the callback's operator( )
boost::asio::async_read_until(
m_socket, *read_buffer, sentinal,
[self = this, read_buffer = std::move( read_buffer ),
on_read_completed = std::forward<OnReadCompletedCallback>( on_read_completed )](
boost::system::error_code const &ec, size_t bytes_transferred ) mutable {
on_read_completed(
*self, std::string_view( static_cast<char const *>( read_buffer->data( ).data( ) ), bytes_transferred ), ec );
read_buffer->consume( bytes_transferred );
} );
}
template<typename OnReadCompletedCallback>
void async_read_some( OnReadCompletedCallback &&on_read_completed ) {
// static_assert(
// std::is_invocable_v<OnReadCompletedCallback, network_client &, std::string_view, boost::system::error_code>
//);
auto read_buffer = std::make_unique<std::array<char, 1024>>( );
std::fill( read_buffer->begin( ), read_buffer->end( ), 0 );
auto d = read_buffer->data( );
size_t s = read_buffer->size( );
// must use mutable because we cannot control the constness of the callback's operator( )
m_socket.async_read_some(
boost::asio::mutable_buffer( d, s ),
[self = this, read_buffer = std::move( read_buffer ),
on_read_completed = std::forward<OnReadCompletedCallback>( on_read_completed )](
boost::system::error_code const &ec, size_t bytes_transferred ) mutable {
on_read_completed(
*self, std::string_view( static_cast<char const *>( read_buffer->data( ) ), bytes_transferred ), ec );
} );
}
};
int main( int, char ** ) {
try {
boost::asio::io_service io_service;
boost::asio::ip::tcp::resolver resolver( io_service );
boost::asio::ssl::context ctx( boost::asio::ssl::context::tlsv12_client );
ctx.set_options( boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 |
boost::asio::ssl::context::no_sslv3 | boost::asio::ssl::context::single_dh_use );
ctx.set_default_verify_paths( );
auto client = network_client(
io_service, ctx, "www.google.ca", 443,
[]( network_client &nc, boost::system::error_code const &ec ) {
if( ec ) {
// Error connecting
std::cerr << "Error connecting: " << ec.message( ) << '\n';
return;
}
nc.async_read_until(
"\r\n\r\n", []( network_client &nc2, std::string_view data, boost::system::error_code const &err_c ) {
if( data.empty( ) && !!err_c ) {
std::cerr << "Error reading data1: " << data.size( ) << " bytes read. " << err_c.message( ) << '\n';
return;
}
std::cout << data << '\n';
nc2.async_write(
std::string( "GET / HTTP1.1\r\nHost: www.google.com\r\nAccept: */*\r\nConnection: close\r\n\r\n" ),
[]( network_client &nc3, boost::system::error_code const &err, size_t /*bytes_trans*/, size_t /*id*/,
auto &&buff ) {
if( !!err ) {
std::cerr << "Error writing data: " << err.message( ) << '\n';
return;
}
nc3.async_read_until( "\r\n\r\n", []( network_client &, std::string_view data2,
boost::system::error_code const &err2 ) {
if( data2.empty( ) && !!err2 ) {
std::cerr << "Error reading data2: " << data2.size( ) << " bytes read. " << err2.message( ) << '\n';
return;
}
std::cout << "result\n--------------\n" << data2 << "\n-------------\n";
} );
} );
} );
},
[]( bool peer_verified, auto && ) { return peer_verified; } );
io_service.run( );
} catch( std::exception &e ) { std::cerr << "Exception: " << e.what( ) << "\n"; }
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment