Rewrote the server in cpp with the frontend in svelte

This commit is contained in:
2023-10-20 13:02:21 +02:00
commit 03b22ebb61
4168 changed files with 831370 additions and 0 deletions

View File

@@ -0,0 +1,321 @@
/*
* Copyright 2013-2020, Corvusoft Ltd, All Rights Reserved.
*/
//System Includes
#include <map>
#include <regex>
#include <sstream>
#include <cstdlib>
#include <clocale>
#include <ciso646>
//Project Includes
#include "corvusoft/restbed/uri.hpp"
#include "corvusoft/restbed/string.hpp"
#include "corvusoft/restbed/request.hpp"
#include "corvusoft/restbed/response.hpp"
#include "corvusoft/restbed/settings.hpp"
#include "corvusoft/restbed/ssl_settings.hpp"
#include "corvusoft/restbed/detail/http_impl.hpp"
#include "corvusoft/restbed/detail/socket_impl.hpp"
#include "corvusoft/restbed/detail/request_impl.hpp"
#ifdef BUILD_IPC
#include "corvusoft/restbed/detail/ipc_socket_impl.hpp"
#endif
//External Includes
#include <asio/error.hpp>
#include <asio/ip/tcp.hpp>
#include <asio/buffer.hpp>
#include <asio/streambuf.hpp>
#ifdef BUILD_SSL
#include <asio/ssl.hpp>
#endif
#ifdef BUILD_IPC
#include <asio/local/stream_protocol.hpp>
#endif
//System Namespaces
using std::free;
using std::bind;
using std::stod;
using std::regex;
using std::smatch;
using std::string;
using std::istream;
using std::function;
using std::multimap;
using std::setlocale;
using std::to_string;
using std::shared_ptr;
using std::error_code;
using std::make_shared;
using std::placeholders::_1;
using std::placeholders::_2;
#ifdef BUILD_IPC
using asio::local::stream_protocol;
#endif
//Project Namespaces
//External Namespaces
using asio::buffer;
using asio::ip::tcp;
using asio::streambuf;
using asio::io_service;
#ifdef BUILD_SSL
using asio::ssl::stream;
#endif
namespace restbed
{
namespace detail
{
Bytes HttpImpl::to_bytes( const shared_ptr< Request >& request )
{
auto path = request->get_path( );
auto parameters = request->get_query_parameters( );
if ( not parameters.empty( ) )
{
string query = String::empty;
for ( const auto parameter : parameters )
{
query += Uri::encode_parameter( parameter.first ) + "=" + Uri::encode_parameter( parameter.second ) + "&";
}
path += "?" + query.substr( 0, query.length( ) - 1 );
}
auto uri = request->m_pimpl->m_uri;
if ( uri not_eq nullptr and not uri->get_fragment( ).empty( ) )
{
path += "#" + uri->get_fragment( );
}
char* locale = nullptr;
if (auto current_locale = setlocale( LC_NUMERIC, nullptr ) )
{
locale = strdup(current_locale);
setlocale( LC_NUMERIC, "C" );
}
auto data = String::format( "%s %s %s/%.1f\r\n",
request->get_method( ).data( ),
path.data( ),
"HTTP",
request->get_version( ) );
if (locale)
{
setlocale( LC_NUMERIC, locale );
free( locale );
}
auto headers = request->get_headers( );
if ( not headers.empty( ) )
{
data += String::join( headers, ": ", "\r\n" ) + "\r\n";
}
data += "\r\n";
auto bytes = String::to_bytes( data );
auto body = request->get_body( );
if ( not body.empty( ) )
{
bytes.insert( bytes.end( ), body.begin( ), body.end( ) );
}
return bytes;
}
void HttpImpl::socket_setup( const shared_ptr< Request >& request, const shared_ptr< const Settings >& settings )
{
if ( request->m_pimpl->m_socket == nullptr )
{
if ( request->m_pimpl->m_io_service == nullptr )
{
request->m_pimpl->m_io_service = make_shared< asio::io_service >( );
}
if ( String::uppercase( request->m_pimpl->m_protocol ) == "HTTP" )
{
auto socket = make_shared< tcp::socket >( *request->m_pimpl->m_io_service );
request->m_pimpl->m_socket = make_shared< SocketImpl >( *request->m_pimpl->m_io_service, socket );
}
#ifdef BUILD_SSL
else if ( String::uppercase( request->m_pimpl->m_protocol ) == "HTTPS" )
{
ssl_socket_setup( request, settings->get_ssl_settings( ) );
}
#endif
#ifdef BUILD_IPC
else if ( String::uppercase( request->m_pimpl->m_protocol ) == "LOCAL" )
{
auto socket = make_shared< stream_protocol::socket >( *request->m_pimpl->m_io_service );
request->m_pimpl->m_socket = make_shared< IPCSocketImpl >( *request->m_pimpl->m_io_service, socket );
}
#endif
else
{
auto socket = make_shared< tcp::socket >( *request->m_pimpl->m_io_service );
request->m_pimpl->m_socket = make_shared< SocketImpl >( *request->m_pimpl->m_io_service, socket );
}
}
request->m_pimpl->m_socket->set_timeout( settings->get_connection_timeout( ) );
}
#ifdef BUILD_SSL
void HttpImpl::ssl_socket_setup( const shared_ptr< Request >& request, const shared_ptr< const SSLSettings >& settings )
{
asio::ssl::context context( asio::ssl::context::sslv23 );
shared_ptr< asio::ssl::stream< asio::ip::tcp::socket > > socket = nullptr;
if ( settings not_eq nullptr )
{
const auto pool = settings->get_certificate_authority_pool( );
if ( pool.empty( ) )
{
context.set_default_verify_paths( );
}
else
{
#ifdef _WIN32
context.load_verify_file(settings->get_certificate_authority_pool());
#else
context.add_verify_path( settings->get_certificate_authority_pool( ) );
#endif
}
socket = make_shared< asio::ssl::stream< asio::ip::tcp::socket > >( *request->m_pimpl->m_io_service, context );
socket->set_verify_mode( asio::ssl::verify_peer | asio::ssl::verify_fail_if_no_peer_cert );
}
else
{
socket = make_shared< asio::ssl::stream< asio::ip::tcp::socket > >( *request->m_pimpl->m_io_service, context );
socket->set_verify_mode( asio::ssl::verify_none );
}
socket->set_verify_callback( asio::ssl::rfc2818_verification( request->get_host( ) ) );
request->m_pimpl->m_socket = make_shared< SocketImpl >( *request->m_pimpl->m_io_service, socket );
}
#endif
void HttpImpl::request_handler( const error_code& error, const shared_ptr< Request >& request, const function< void ( const shared_ptr< Request >, const shared_ptr< Response > ) >& callback )
{
if ( error )
{
const auto body = String::format( "Failed to locate HTTP endpoint: %s", error.message( ).data( ) );
return callback( request, create_error_response( request, body ) );
}
request->m_pimpl->m_socket->start_write( to_bytes( request ), bind( write_handler, _1, _2, request, callback ) );
}
void HttpImpl::write_handler( const error_code& error, const size_t, const shared_ptr< Request >& request, const function< void ( const shared_ptr< Request >, const shared_ptr< Response > ) >& callback )
{
if ( error )
{
const auto body = String::format( "Socket write failed: %s", error.message( ).data( ) );
return callback( request, create_error_response( request, body ) );
}
request->m_pimpl->m_buffer = make_shared< asio::streambuf >( );
request->m_pimpl->m_socket->start_read( request->m_pimpl->m_buffer, "\r\n", bind( read_status_handler, _1, _2, request, callback ) );
}
const shared_ptr< Response > HttpImpl::create_error_response( const shared_ptr< Request >& request, const string& message )
{
auto response = request->m_pimpl->m_response;
response->set_protocol( request->get_protocol( ) );
response->set_version( request->get_version( ) );
response->set_status_code( 0 );
response->set_status_message( "Error" );
response->set_header( "Content-Type", "text/plain; utf-8" );
response->set_header( "Content-Length", ::to_string( message.length( ) ) );
response->set_body( message );
return response;
}
void HttpImpl::read_status_handler( const error_code& error, const size_t, const shared_ptr< Request >& request, const function< void ( const shared_ptr< Request >, const shared_ptr< Response > ) >& callback )
{
if ( error )
{
const auto body = String::format( "Failed to read HTTP response status line: %s", error.message( ).data( ) );
return callback( request, create_error_response( request, body ) );
}
istream response_stream( request->m_pimpl->m_buffer.get( ) );
string status_line = String::empty;
getline( response_stream, status_line );
smatch matches;
static const regex status_line_pattern( "^([a-zA-Z]+)\\/(\\d*\\.?\\d*) (-?\\d+) (.*)\\r$" );
if ( not regex_match( status_line, matches, status_line_pattern ) or matches.size( ) not_eq 5 )
{
const auto body = String::format( "HTTP response status line malformed: '%s'", status_line.data( ) );
return callback( request, create_error_response( request, body ) );
}
auto response = request->m_pimpl->m_response;
response->set_protocol( matches[ 1 ].str( ) );
response->set_version( stod( matches[ 2 ].str( ) ) );
response->set_status_code( stoi( matches[ 3 ].str( ) ) );
response->set_status_message( matches[ 4 ].str( ) );
request->m_pimpl->m_socket->start_read( request->m_pimpl->m_buffer, "\r\n\r\n", bind( read_headers_handler, _1, _2, request, callback ) );
}
void HttpImpl::read_headers_handler( const error_code& error, const size_t, const shared_ptr< Request >& request, const function< void ( const shared_ptr< Request >, const shared_ptr< Response > ) >& callback )
{
if ( error == asio::error::eof )
{
return callback( request, request->m_pimpl->m_response );
}
if ( error )
{
const auto body = String::format( "Failed to read HTTP response status headers: '%s'", error.message( ).data( ) );
return callback( request, create_error_response( request, body ) );
}
string header = String::empty;
multimap< string, string > headers = { };
istream response_stream( request->m_pimpl->m_buffer.get( ) );
while ( getline( response_stream, header ) and header not_eq "\r" )
{
static const regex header_pattern( "^([^:.]*): *(.*)\\s*$" );
smatch matches;
if ( not regex_match( header, matches, header_pattern ) or matches.size( ) not_eq 3 )
{
const auto body = String::format( "Malformed HTTP response header: '%s'", header.data( ) );
return callback( request, create_error_response( request, body ) );
}
headers.insert( make_pair( matches[ 1 ], matches[ 2 ] ) );
}
auto response = request->m_pimpl->m_response;
response->set_headers( headers );
callback( request, response );
}
}
}

View File

@@ -0,0 +1,110 @@
/*
* Copyright 2013-2020, Corvusoft Ltd, All Rights Reserved.
*/
#pragma once
//System Includes
#include <string>
#include <memory>
#include <functional>
#include <system_error>
//Project Includes
#include <corvusoft/restbed/byte.hpp>
//External Includes
//System Namespaces
//Project Namespaces
//External Namespaces
namespace restbed
{
//Fordward Declarations
class Request;
class Response;
class Settings;
class SSLSettings;
namespace detail
{
//Forward Declarations
class HttpImpl
{
public:
//Friends
//Definitions
//Constructors
//Functionality
static Bytes to_bytes( const std::shared_ptr< Request >& value );
static void socket_setup( const std::shared_ptr< Request >& request, const std::shared_ptr< const Settings >& settings );
#ifdef BUILD_SSL
static void ssl_socket_setup( const std::shared_ptr< Request >& request, const std::shared_ptr< const SSLSettings >& settings );
#endif
static void request_handler( const std::error_code& error, const std::shared_ptr< Request >& request, const std::function< void ( const std::shared_ptr< Request >, const std::shared_ptr< Response > ) >& callback );
static void write_handler( const std::error_code& error, const std::size_t length, const std::shared_ptr< Request >& request, const std::function< void ( const std::shared_ptr< Request >, const std::shared_ptr< Response > ) >& callback );
//Getters
//Setters
//Operators
//Properties
protected:
//Friends
//Definitions
//Constructors
//Functionality
//Getters
//Setters
//Operators
//Properties
private:
//Friends
//Definitions
//Constructors
HttpImpl( void ) = delete;
virtual ~HttpImpl( void ) = delete;
HttpImpl( const HttpImpl& original ) = delete;
//Functionality
static const std::shared_ptr< Response > create_error_response( const std::shared_ptr< Request >& request, const std::string& message );
static void read_status_handler( const std::error_code& error, const std::size_t length, const std::shared_ptr< Request >& request, const std::function< void ( const std::shared_ptr< Request >, const std::shared_ptr< Response > ) >& callback );
static void read_headers_handler( const std::error_code& error, const std::size_t length, const std::shared_ptr< Request >& request, const std::function< void ( const std::shared_ptr< Request >, const std::shared_ptr< Response > ) >& callback );
//Getters
//Setters
//Operators
HttpImpl& operator =( const HttpImpl& value ) = delete;
//Properties
};
}
}

View File

@@ -0,0 +1,393 @@
/*
* Copyright 2013-2020, Corvusoft Ltd, All Rights Reserved.
*/
#ifdef BUILD_IPC
//System Includes
#include <future>
#include <ciso646>
//Project Includes
#include "corvusoft/restbed/logger.hpp"
#include "corvusoft/restbed/detail/socket_impl.hpp"
#include "corvusoft/restbed/detail/ipc_socket_impl.hpp"
//External Includes
#include <asio/read.hpp>
#include <asio/write.hpp>
#include <asio/connect.hpp>
#include <asio/read_until.hpp>
//System Namespaces
using std::get;
using std::bind;
using std::size_t;
using std::string;
using std::promise;
using std::function;
using std::to_string;
using std::error_code;
using std::shared_ptr;
using std::make_shared;
using std::runtime_error;
using std::placeholders::_1;
using std::chrono::milliseconds;
using std::chrono::steady_clock;
//Project Namespaces
using restbed::detail::IPCSocketImpl;
//External Namespaces
using asio::io_service;
using asio::steady_timer;
using asio::local::stream_protocol;
namespace restbed
{
namespace detail
{
IPCSocketImpl::IPCSocketImpl( asio::io_context& context, const shared_ptr< stream_protocol::socket >& socket, const shared_ptr< Logger >& logger ) : SocketImpl( context ),
m_error_handler( nullptr ),
m_is_open( socket->is_open( ) ),
m_pending_writes( ),
m_logger( logger ),
m_timeout( 0 ),
m_io_service( context ),
m_timer( make_shared< asio::steady_timer >( m_io_service ) ),
m_strand( make_shared< io_service::strand > ( m_io_service ) ),
m_socket( socket )
{
return;
}
void IPCSocketImpl::close( void )
{
m_is_open = false;
if ( m_timer not_eq nullptr )
{
m_timer->cancel( );
}
if ( m_socket not_eq nullptr )
{
m_socket->close( );
}
}
bool IPCSocketImpl::is_open( void ) const
{
return m_is_open;
}
bool IPCSocketImpl::is_closed( void ) const
{
return not m_is_open;
}
void IPCSocketImpl::connect( const string&, const uint16_t, const function< void ( const error_code& ) >& callback )
{
m_socket->async_connect( stream_protocol::endpoint("/tmp/restbed.sock"), [ this, callback ]( const error_code & error )
{
m_is_open = true;
callback( error );
} );
}
void IPCSocketImpl::sleep_for( const milliseconds& delay, const function< void ( const error_code& ) >& callback )
{
m_timer->cancel( );
m_timer->expires_from_now( delay );
m_timer->async_wait( callback );
}
void IPCSocketImpl::start_write(const Bytes& data, const std::function< void ( const std::error_code&, std::size_t ) >& callback)
{
m_strand->post([this, data, callback] { write_helper(data, callback); });
}
size_t IPCSocketImpl::start_read(const shared_ptr< asio::streambuf >& data, const string& delimiter, error_code& error)
{
return read( data, delimiter,error );
}
size_t IPCSocketImpl::start_read(const shared_ptr< asio::streambuf >& data, const size_t length, error_code& error)
{
return read( data, length, error );
}
void IPCSocketImpl::start_read( const std::size_t length, const function< void ( const Bytes ) > success, const function< void ( const error_code ) > failure )
{
m_strand->post([this, length, success, failure] {
read(length, success, failure);
});
}
void IPCSocketImpl::start_read(const shared_ptr< asio::streambuf >& data, const size_t length, const function< void ( const error_code&, size_t ) >& callback)
{
m_strand->post([this, data, length, callback]
{
read(data, length, callback);
});
}
void IPCSocketImpl::start_read(const shared_ptr< asio::streambuf >& data, const string& delimiter, const function< void ( const error_code&, size_t ) >& callback)
{
m_strand->post([this, data, delimiter, callback]
{
read(data, delimiter, callback);
});
}
string IPCSocketImpl::get_local_endpoint( void )
{
return "/tmp/restbed.sock";
}
string IPCSocketImpl::get_remote_endpoint( void )
{
return "/tmp/restbed.sock";
}
void IPCSocketImpl::set_timeout( const milliseconds& value )
{
m_timeout = value;
}
void IPCSocketImpl::set_keep_alive( const uint32_t, const uint32_t, const uint32_t)
{
return;
}
shared_ptr< IPCSocketImpl > IPCSocketImpl::shared_from_this( void )
{
return std::dynamic_pointer_cast< IPCSocketImpl >( SocketImpl::shared_from_this( ) );
//return shared_ptr< IPCSocketImpl >( this ); //test for circular reference and memory leak.
}
void IPCSocketImpl::connection_timeout_handler( const shared_ptr< IPCSocketImpl > socket, const error_code& error )
{
if ( error or socket == nullptr or socket->m_timer->expires_at( ) > steady_clock::now( ) )
{
return;
}
socket->close( );
if ( m_error_handler not_eq nullptr )
{
m_error_handler( 408, runtime_error( "The socket timed out waiting for the request." ), nullptr );
}
}
void IPCSocketImpl::write( void )
{
if(m_is_open)
{
m_timer->cancel( );
m_timer->expires_from_now( m_timeout );
m_timer->async_wait( m_strand->wrap( bind( &IPCSocketImpl::connection_timeout_handler, this, shared_from_this( ), _1 ) ) );
asio::async_write( *m_socket, asio::buffer( get<0>(m_pending_writes.front()).data( ), get<0>(m_pending_writes.front()).size( ) ), m_strand->wrap( [ this ]( const error_code & error, size_t length )
{
m_timer->cancel( );
auto callback = get<2>(m_pending_writes.front());
auto & retries = get<1>(m_pending_writes.front());
auto & buffer = get<0>(m_pending_writes.front());
if(length < buffer.size() && retries < MAX_WRITE_RETRIES && error not_eq asio::error::operation_aborted)
{
++retries;
buffer.erase(buffer.begin(),buffer.begin() + length);
}
else
{
m_pending_writes.pop();
}
if ( error not_eq asio::error::operation_aborted )
{
callback( error, length );
}
if(!m_pending_writes.empty())
{
write();
}
} ) );
}
else
{
while(!m_pending_writes.empty())
{
m_pending_writes.pop();
}
}
}
void IPCSocketImpl::write( const Bytes& data, const function< void ( const error_code&, size_t ) >& callback )
{
const auto buffer = make_shared< Bytes >( data );
m_timer->cancel( );
m_timer->expires_from_now( m_timeout );
m_timer->async_wait( m_strand->wrap( bind( &IPCSocketImpl::connection_timeout_handler, this, shared_from_this( ), _1 ) ) );
asio::async_write( *m_socket, asio::buffer( buffer->data( ), buffer->size( ) ), m_strand->wrap( [ this, callback, buffer ]( const error_code & error, size_t length )
{
m_timer->cancel( );
if ( error )
{
m_is_open = false;
}
if ( error not_eq asio::error::operation_aborted )
{
callback( error, length );
}
} ) );
}
void IPCSocketImpl::write_helper(const Bytes& data, const function< void ( const error_code&, size_t ) >& callback)
{
const uint8_t retries = 0;
m_pending_writes.push(make_tuple(data, retries, callback));
if(m_pending_writes.size() == 1)
{
write();
}
}
size_t IPCSocketImpl::read( const shared_ptr< asio::streambuf >& data, const size_t length, error_code& error )
{
m_timer->cancel( );
m_timer->expires_from_now( m_timeout );
m_timer->async_wait( m_strand->wrap( bind( &IPCSocketImpl::connection_timeout_handler, this, shared_from_this( ), _1 ) ) );
size_t size = 0;
auto finished = std::make_shared<bool>(false);
auto sharedError = std::make_shared<error_code>();
auto sharedSize = std::make_shared<size_t>(0);
asio::async_read( *m_socket, *data, asio::transfer_at_least( length ),
[ finished, sharedSize, sharedError ]( const error_code & error, size_t size ) {
*sharedError = error;
*sharedSize = size;
*finished = true;
});
while (!*finished)
m_io_service.run_one();
error = *sharedError;
size = *sharedSize;
m_timer->cancel( );
if ( error )
{
m_is_open = false;
}
return size;
}
void IPCSocketImpl::read( const std::size_t length, const function< void ( const Bytes ) > success, const function< void ( const error_code ) > failure )
{
m_timer->cancel( );
m_timer->expires_from_now( m_timeout );
m_timer->async_wait( m_strand->wrap( bind( &IPCSocketImpl::connection_timeout_handler, this, shared_from_this( ), _1 ) ) );
auto data = make_shared< asio::streambuf >( );
asio::async_read( *m_socket, *data, asio::transfer_exactly( length ), [ this, data, success, failure ]( const error_code code, const size_t length )
{
m_timer->cancel( );
if ( code )
{
m_is_open = false;
failure( code );
}
else
{
const auto data_ptr = asio::buffer_cast< const Byte* >( data->data( ) );
success( Bytes( data_ptr, data_ptr + length ) );
}
} );
}
void IPCSocketImpl::read( const shared_ptr< asio::streambuf >& data, const size_t length, const function< void ( const error_code&, size_t ) >& callback )
{
m_timer->cancel( );
m_timer->expires_from_now( m_timeout );
m_timer->async_wait( m_strand->wrap( bind( &IPCSocketImpl::connection_timeout_handler, this, shared_from_this( ), _1 ) ) );
asio::async_read( *m_socket, *data, asio::transfer_at_least( length ), m_strand->wrap( [ this, callback ]( const error_code & error, size_t length )
{
m_timer->cancel( );
if ( error )
{
m_is_open = false;
}
if ( error not_eq asio::error::operation_aborted )
{
callback( error, length );
}
} ) );
}
size_t IPCSocketImpl::read( const shared_ptr< asio::streambuf >& data, const string& delimiter, error_code& error )
{
m_timer->cancel( );
m_timer->expires_from_now( m_timeout );
m_timer->async_wait( bind( &IPCSocketImpl::connection_timeout_handler, this, shared_from_this( ), _1 ) );
size_t length = 0;
auto finished = std::make_shared<bool>(false);
auto sharedError = std::make_shared<error_code>();
auto sharedLength = std::make_shared<size_t>(0);
asio::async_read_until( *m_socket, *data, delimiter,
[ finished, sharedLength, sharedError ]( const error_code & error, size_t length ) {
*sharedError = error;
*sharedLength = length;
*finished = true;
});
while (!*finished)
m_io_service.run_one();
error = *sharedError;
length = *sharedLength;
m_timer->cancel( );
if ( error )
{
m_is_open = false;
}
return length;
}
void IPCSocketImpl::read( const shared_ptr< asio::streambuf >& data, const string& delimiter, const function< void ( const error_code&, size_t ) >& callback )
{
m_timer->cancel( );
m_timer->expires_from_now( m_timeout );
m_timer->async_wait( m_strand->wrap( bind( &IPCSocketImpl::connection_timeout_handler, this, shared_from_this( ), _1 ) ) );
asio::async_read_until( *m_socket, *data, delimiter, m_strand->wrap( [ this, callback ]( const error_code & error, size_t length )
{
m_timer->cancel( );
if ( error )
{
m_is_open = false;
}
if ( error not_eq asio::error::operation_aborted )
{
callback( error, length );
}
} ) );
}
}
}
#endif

View File

@@ -0,0 +1,172 @@
/*
* Copyright 2013-2020, Corvusoft Ltd, All Rights Reserved.
*/
#pragma once
#ifdef BUILD_IPC
//System Includes
#include <queue>
#include <tuple>
#include <chrono>
#include <string>
#include <memory>
#include <cstdint>
#include <stdexcept>
#include <functional>
#include <system_error>
//Project Includes
#include "corvusoft/restbed/byte.hpp"
#include "corvusoft/restbed/detail/socket_impl.hpp"
//External Includes
#include <asio/streambuf.hpp>
#include <asio/steady_timer.hpp>
#include <asio/io_service.hpp>
#include <asio/io_service_strand.hpp>
#include <asio/local/stream_protocol.hpp>
//System Namespaces
//Project Namespaces
//External Namespaces
namespace restbed
{
//Forward Declarations
class Logger;
class Session;
namespace detail
{
//Forward Declarations
class IPCSocketImpl : virtual public SocketImpl
{
public:
//Friends
//Definitions
//Constructors
IPCSocketImpl( asio::io_context& context, const std::shared_ptr< asio::local::stream_protocol::socket >& socket, const std::shared_ptr< Logger >& logger = nullptr );
~IPCSocketImpl( void ) = default;
//Functionality
void close( void ) override;
bool is_open( void ) const override;
bool is_closed( void ) const override;
void connect( const std::string& hostname, const uint16_t port, const std::function< void ( const std::error_code& ) >& callback ) override;
void sleep_for( const std::chrono::milliseconds& delay, const std::function< void ( const std::error_code& ) >& callback ) override;
void start_write(const Bytes& data, const std::function< void ( const std::error_code&, std::size_t ) >& callback) override;
size_t start_read( const std::shared_ptr< asio::streambuf >& data, const std::string& delimiter, std::error_code& error ) override;
size_t start_read( const std::shared_ptr< asio::streambuf >& data, const std::size_t length, std::error_code& error ) override;
void start_read(const std::size_t length, const std::function< void ( const Bytes ) > success, const std::function< void ( const std::error_code ) > failure ) override;
void start_read( const std::shared_ptr< asio::streambuf >& data, const std::size_t length, const std::function< void ( const std::error_code&, std::size_t ) >& callback ) override;
void start_read(const std::shared_ptr< asio::streambuf >& data, const std::string& delimiter, const std::function< void ( const std::error_code&, std::size_t ) >& callback ) override;
//Getters
std::string get_local_endpoint( void ) override;
std::string get_remote_endpoint( void ) override;
//Setters
void set_timeout( const std::chrono::milliseconds& value ) override;
void set_keep_alive( const uint32_t start, const uint32_t interval, const uint32_t cnt) override;
//Operators
//Properties
std::function< void ( const int, const std::exception&, const std::shared_ptr< Session > ) > m_error_handler;
protected:
//Friends
//Definitions
//Constructors
//Functionality
//Getters
//Setters
//Operators
//Properties
private:
//Friends
//Definitions
//Constructors
IPCSocketImpl( const IPCSocketImpl& original ) = delete;
//Functionality
std::shared_ptr< IPCSocketImpl > shared_from_this( void );
void connection_timeout_handler( const std::shared_ptr< IPCSocketImpl > socket, const std::error_code& error );
void write( void );
void write( const Bytes& data, const std::function< void ( const std::error_code&, std::size_t ) >& callback );
void write_helper( const Bytes& data, const std::function< void ( const std::error_code&, std::size_t ) >& callback );
size_t read( const std::shared_ptr< asio::streambuf >& data, const std::size_t length, std::error_code& error );
void read( const std::size_t length, const std::function< void ( const Bytes ) > success, const std::function< void ( const std::error_code ) > failure );
void read( const std::shared_ptr< asio::streambuf >& data, const std::size_t length, const std::function< void ( const std::error_code&, std::size_t ) >& callback );
size_t read( const std::shared_ptr< asio::streambuf >& data, const std::string& delimiter, std::error_code& error );
void read( const std::shared_ptr< asio::streambuf >& data, const std::string& delimiter, const std::function< void ( const std::error_code&, std::size_t ) >& callback );
//Getters
//Setters
//Operators
IPCSocketImpl& operator =( const IPCSocketImpl& value ) = delete;
//Properties
bool m_is_open;
const uint8_t MAX_WRITE_RETRIES = 5;
std::queue< std::tuple< Bytes, uint8_t, std::function< void ( const std::error_code&, std::size_t ) > > > m_pending_writes;
std::shared_ptr< Logger > m_logger;
std::chrono::milliseconds m_timeout;
asio::io_context &m_io_service;
std::shared_ptr< asio::steady_timer > m_timer;
std::shared_ptr< asio::io_service::strand > m_strand;
std::shared_ptr< asio::local::stream_protocol::socket > m_socket;
};
}
}
#endif

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2013-2020, Corvusoft Ltd, All Rights Reserved.
*/
#pragma once
//System Includes
#include <map>
#include <string>
#include <memory>
#include <cstdint>
//Project Includes
#include <corvusoft/restbed/byte.hpp>
//External Includes
#include <asio/streambuf.hpp>
#include <asio/io_service.hpp>
//System Namespaces
//Project Namespaces
//External Namespaces
namespace restbed
{
//Forward Declarations
class Uri;
class Response;
namespace detail
{
//Forward Declarations
class SocketImpl;
struct RequestImpl
{
Bytes m_body { };
uint16_t m_port = 80;
double m_version = 1.1;
std::string m_host = "";
std::string m_path = "/";
std::string m_method = "GET";
std::string m_protocol = "HTTP";
std::shared_ptr< Uri > m_uri = nullptr;
std::shared_ptr< Response > m_response = nullptr;
std::multimap< std::string, std::string > m_headers { };
std::map< std::string, std::string > m_path_parameters { };
std::multimap< std::string, std::string > m_query_parameters { };
std::shared_ptr< asio::io_service > m_io_service = nullptr;
std::shared_ptr< SocketImpl > m_socket = nullptr;
std::shared_ptr< asio::streambuf > m_buffer = nullptr;
};
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2013-2020, Corvusoft Ltd, All Rights Reserved.
*/
#pragma once
//System Includes
#include <map>
#include <set>
#include <string>
#include <vector>
#include <functional>
//Project Includes
//External Includes
//System Namespaces
//Project Namespaces
//External Namespaces
namespace restbed
{
//Forward Declarations
class Rule;
class Session;
namespace detail
{
//Forward Declarations
struct ResourceImpl
{
std::set< std::string > m_paths { };
std::set< std::string > m_methods { };
std::vector< std::shared_ptr< Rule > > m_rules { };
std::multimap< std::string, std::string > m_default_headers { };
std::function< void ( const std::shared_ptr< Session > ) > m_failed_filter_validation_handler = nullptr;
std::function< void ( const int, const std::exception&, const std::shared_ptr< Session > ) > m_error_handler = nullptr;
std::function< void ( const std::shared_ptr< Session >, const std::function< void ( const std::shared_ptr< Session > ) >& ) > m_authentication_handler = nullptr;
std::multimap< std::string, std::pair< std::multimap< std::string, std::string >, std::function< void ( const std::shared_ptr< Session > ) > > > m_method_handlers { };
};
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2013-2020, Corvusoft Ltd, All Rights Reserved.
*/
#pragma once
//System Includes
#include <map>
#include <memory>
#include <string>
//Project Includes
#include "corvusoft/restbed/byte.hpp"
//External Includes
//System Namespaces
//Project Namespaces
//External Namespaces
namespace restbed
{
//Forward Declarations
class Request;
namespace detail
{
//Forward Declarations
struct ResponseImpl
{
Bytes m_body { };
double m_version = 1.1;
int m_status_code = 0;
Request* m_request = nullptr;
std::string m_protocol = "HTTP";
std::string m_status_message = "";
std::multimap< std::string, std::string > m_headers { };
};
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2013-2020, Corvusoft Ltd, All Rights Reserved.
*/
#pragma once
//System Includes
#include <vector>
#include <memory>
#include <cstddef>
//Project Includes
//External Includes
//System Namespaces
//Project Namespaces
//External Namespaces
namespace restbed
{
//Forward Declarations
class Rule;
class Session;
namespace detail
{
//Forward Declarations
static void rule_engine( const std::shared_ptr< Session > session,
const std::vector< std::shared_ptr< Rule > >& rules,
const std::function< void ( const std::shared_ptr< Session > ) >& callback,
std::size_t index = 0 )
{
while ( index not_eq rules.size( ) )
{
auto rule = rules.at( index );
index++;
if ( rule->condition( session ) )
{
rule->action( session, bind( rule_engine, session, rules, callback, index ) );
return;
}
}
if ( index == rules.size( ) )
{
callback( session );
}
}
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2013-2020, Corvusoft Ltd, All Rights Reserved.
*/
#pragma once
//System Includes
#include <memory>
//Project Includes
//External Includes
//System Namespaces
//Project Namespaces
//External Namespaces
namespace restbed
{
//Forward Declarations
namespace detail
{
//Forward Declarations
struct RuleImpl
{
int m_priority = 0;
};
}
}

View File

@@ -0,0 +1,951 @@
/*
* Copyright 2013-2020, Corvusoft Ltd, All Rights Reserved.
*/
//System Includes
#include <regex>
#include <cstdio>
#include <sstream>
#include <utility>
#include <ciso646>
#include <cstdlib>
#include <clocale>
#include <stdexcept>
#include <algorithm>
#include <functional>
//Project Includes
#include "corvusoft/restbed/uri.hpp"
#include "corvusoft/restbed/rule.hpp"
#include "corvusoft/restbed/logger.hpp"
#include "corvusoft/restbed/string.hpp"
#include "corvusoft/restbed/request.hpp"
#include "corvusoft/restbed/session.hpp"
#include "corvusoft/restbed/resource.hpp"
#include "corvusoft/restbed/settings.hpp"
#include "corvusoft/restbed/status_code.hpp"
#include "corvusoft/restbed/ssl_settings.hpp"
#include "corvusoft/restbed/session_manager.hpp"
#include "corvusoft/restbed/detail/socket_impl.hpp"
#include "corvusoft/restbed/detail/request_impl.hpp"
#include "corvusoft/restbed/detail/service_impl.hpp"
#include "corvusoft/restbed/detail/session_impl.hpp"
#include "corvusoft/restbed/detail/resource_impl.hpp"
#include "corvusoft/restbed/detail/ipc_socket_impl.hpp"
#include "corvusoft/restbed/detail/rule_engine_impl.hpp"
#include "corvusoft/restbed/detail/web_socket_manager_impl.hpp"
//External Includes
//System Namespaces
using std::set;
using std::map;
using std::free;
using std::pair;
using std::bind;
using std::regex;
using std::string;
using std::smatch;
using std::istream;
using std::find_if;
using std::function;
using std::setlocale;
using std::multimap;
using std::to_string;
using std::exception;
using std::shared_ptr;
using std::error_code;
using std::regex_error;
using std::make_shared;
using std::runtime_error;
using std::placeholders::_1;
using std::placeholders::_2;
using std::placeholders::_3;
using std::current_exception;
using std::rethrow_exception;
using std::chrono::steady_clock;
using std::regex_constants::icase;
//Project Namespaces
//External Namespaces
using asio::ip::tcp;
using asio::io_service;
using asio::signal_set;
using asio::ip::address;
using asio::socket_base;
using asio::system_error;
#if BUILD_IPC
using asio::local::stream_protocol;
#endif
namespace restbed
{
namespace detail
{
ServiceImpl::ServiceImpl( void ) : m_uptime( steady_clock::time_point::min( ) ),
m_logger( nullptr ),
m_supported_methods( ),
m_settings( nullptr ),
m_io_service( make_shared< ::io_service >( ) ),
m_signal_set( nullptr ),
m_session_manager( nullptr ),
m_web_socket_manager( nullptr ),
m_rules( ),
m_workers_stopped( ),
#ifdef BUILD_SSL
m_ssl_settings( nullptr ),
m_ssl_context( nullptr ),
m_ssl_acceptor( nullptr ),
#endif
#ifdef BUILD_IPC
m_ipc_acceptor( nullptr ),
#endif
m_acceptor( nullptr ),
m_resource_paths( ),
m_resource_routes( ),
m_ready_handler( nullptr ),
m_signal_handlers( ),
m_not_found_handler( nullptr ),
m_method_not_allowed_handler( nullptr ),
m_method_not_implemented_handler( nullptr ),
m_failed_filter_validation_handler( nullptr ),
m_error_handler( ServiceImpl::default_error_handler ),
m_authentication_handler( nullptr )
{
return;
}
ServiceImpl::~ServiceImpl( void )
{
return;
}
void ServiceImpl::http_start( void )
{
#ifdef BUILD_SSL
if ( m_ssl_settings == nullptr or m_ssl_settings->has_disabled_http( ) == false )
{
#endif
if ( not m_settings->get_bind_address( ).empty( ) )
{
const auto address = address::from_string( m_settings->get_bind_address( ) );
m_acceptor = make_shared< tcp::acceptor >( *m_io_service, tcp::endpoint( address, m_settings->get_port( ) ) );
}
else
{
m_acceptor = make_shared< tcp::acceptor >( *m_io_service, tcp::endpoint( tcp::v6( ), m_settings->get_port( ) ) );
}
m_acceptor->set_option( socket_base::reuse_address( m_settings->get_reuse_address( ) ) );
m_acceptor->listen( m_settings->get_connection_limit( ) );
http_listen( );
const auto location = get_http_uri( )->to_string( );
log( Logger::INFO, String::format( "Service accepting HTTP connections at '%s'.", location.data( ) ) );
#ifdef BUILD_SSL
}
#endif
}
void ServiceImpl::http_listen( void ) const
{
auto socket = make_shared< tcp::socket >( *m_io_service );
m_acceptor->async_accept( *socket, bind( &ServiceImpl::create_session, this, socket, _1 ) );
}
void ServiceImpl::setup_signal_handler( void )
{
if ( m_signal_handlers.empty( ) )
{
return;
}
m_signal_set = make_shared< signal_set >( *m_io_service );
for ( const auto signal_handler : m_signal_handlers )
{
m_signal_set->add( signal_handler.first );
}
m_signal_set->async_wait( bind( &ServiceImpl::signal_handler, this, _1, _2 ) );
}
void ServiceImpl::signal_handler( const error_code& error, const int signal_number ) const
{
if ( error )
{
log( Logger::WARNING, String::format( "Failed to process signal '%i', '%s'.", signal_number, error.message( ).data( ) ) );
return;
}
if ( m_signal_handlers.count( signal_number ) )
{
m_signal_handlers.at( signal_number )( signal_number );
}
m_signal_set->async_wait( bind( &ServiceImpl::signal_handler, this, _1, _2 ) );
}
#ifdef BUILD_SSL
void ServiceImpl::https_start( void )
{
if ( m_ssl_settings not_eq nullptr )
{
m_ssl_context = make_shared< asio::ssl::context >( asio::ssl::context::sslv23 );
m_ssl_context->set_default_verify_paths( );
auto passphrase = m_ssl_settings->get_passphrase( );
m_ssl_context->set_password_callback( [ passphrase ]( size_t, asio::ssl::context::password_purpose )
{
return passphrase;
} );
auto filename = m_ssl_settings->get_temporary_diffie_hellman( );
if ( not filename.empty( ) )
{
m_ssl_context->use_tmp_dh_file( filename );
}
filename = m_ssl_settings->get_certificate_authority_pool( );
if ( not filename.empty( ) )
{
m_ssl_context->add_verify_path( filename );
}
filename = m_ssl_settings->get_certificate_chain( );
if ( not filename.empty( ) )
{
m_ssl_context->use_certificate_chain_file( filename );
}
filename = m_ssl_settings->get_certificate( );
if ( not filename.empty( ) )
{
m_ssl_context->use_certificate_file( filename, asio::ssl::context::pem );
}
filename = m_ssl_settings->get_private_key( );
if ( not filename.empty( ) )
{
m_ssl_context->use_private_key_file( filename, asio::ssl::context::pem );
}
filename = m_ssl_settings->get_private_rsa_key( );
if ( not filename.empty( ) )
{
m_ssl_context->use_rsa_private_key_file( filename, asio::ssl::context::pem );
}
asio::ssl::context::options options = 0;
options = ( m_ssl_settings->has_enabled_tlsv1( ) ) ? options : options | asio::ssl::context::no_tlsv1;
options = ( m_ssl_settings->has_enabled_sslv2( ) ) ? options : options | asio::ssl::context::no_sslv2;
options = ( m_ssl_settings->has_enabled_sslv3( ) ) ? options : options | asio::ssl::context::no_sslv3;
options = ( m_ssl_settings->has_enabled_tlsv11( ) ) ? options : options | asio::ssl::context::no_tlsv1_1;
options = ( m_ssl_settings->has_enabled_tlsv12( ) ) ? options : options | asio::ssl::context::no_tlsv1_2;
options = ( m_ssl_settings->has_enabled_compression( ) ) ? options : options | asio::ssl::context::no_compression;
options = ( m_ssl_settings->has_enabled_default_workarounds( ) ) ? options | asio::ssl::context::default_workarounds : options;
options = ( m_ssl_settings->has_enabled_single_diffie_hellman_use( ) ) ? options | asio::ssl::context::single_dh_use : options;
m_ssl_context->set_options( options );
if ( not m_ssl_settings->get_bind_address( ).empty( ) )
{
const auto address = address::from_string( m_ssl_settings->get_bind_address( ) );
m_ssl_acceptor = make_shared< tcp::acceptor >( *m_io_service, tcp::endpoint( address, m_ssl_settings->get_port( ) ) );
}
else
{
m_ssl_acceptor = make_shared< tcp::acceptor >( *m_io_service, tcp::endpoint( tcp::v6( ), m_ssl_settings->get_port( ) ) );
}
m_ssl_acceptor->set_option( socket_base::reuse_address( m_settings->get_reuse_address( ) ) );
m_ssl_acceptor->listen( m_settings->get_connection_limit( ) );
https_listen( );
const auto location = get_https_uri( )->to_string( );
log( Logger::INFO, String::format( "Service accepting HTTPS connections at '%s'.", location.data( ) ) );
}
}
void ServiceImpl::https_listen( void ) const
{
auto socket = make_shared< asio::ssl::stream< tcp::socket > >( *m_io_service, *m_ssl_context );
m_ssl_acceptor->async_accept( socket->lowest_layer( ), bind( &ServiceImpl::create_ssl_session, this, socket, _1 ) );
}
void ServiceImpl::create_ssl_session( const shared_ptr< asio::ssl::stream< tcp::socket > >& socket, const error_code& error ) const
{
if ( not error )
{
socket->async_handshake( asio::ssl::stream_base::server, [ this, socket ]( const error_code & error )
{
if ( error )
{
log( Logger::SECURITY, String::format( "Failed SSL handshake, '%s'.", error.message( ).data( ) ) );
return;
}
auto connection = make_shared< SocketImpl >( *m_io_service, socket, m_logger );
connection->set_timeout( m_settings->get_connection_timeout( ) );
if (m_settings->get_keep_alive()) {
connection->set_keep_alive( m_settings->get_keep_alive_start(),
m_settings->get_keep_alive_interval(),
m_settings->get_keep_alive_cnt());
}
m_session_manager->create( [ this, connection ]( const shared_ptr< Session > session )
{
session->m_pimpl->m_settings = m_settings;
session->m_pimpl->m_manager = m_session_manager;
session->m_pimpl->m_web_socket_manager = m_web_socket_manager;
session->m_pimpl->m_error_handler = m_error_handler;
session->m_pimpl->m_request = make_shared< Request >( );
session->m_pimpl->m_request->m_pimpl->m_socket = connection;
session->m_pimpl->m_request->m_pimpl->m_socket->m_error_handler = m_error_handler;
session->m_pimpl->m_request->m_pimpl->m_buffer = make_shared< asio::streambuf >( );
session->m_pimpl->m_keep_alive_callback = bind( &ServiceImpl::parse_request, this, _1, _2, _3 );
session->m_pimpl->m_request->m_pimpl->m_socket->start_read( session->m_pimpl->m_request->m_pimpl->m_buffer, "\r\n\r\n", bind( &ServiceImpl::parse_request, this, _1, _2, session ) );
} );
} );
}
else
{
if ( socket not_eq nullptr and socket->lowest_layer( ).is_open( ) )
{
socket->lowest_layer( ).close( );
}
log( Logger::WARNING, String::format( "Failed to create session, '%s'.", error.message( ).data( ) ) );
}
https_listen( );
}
#endif
#ifdef BUILD_IPC
void ServiceImpl::ipc_start( void )
{
const string location = "/tmp/restbed.sock";
::remove( location.data( ) );
m_ipc_acceptor = make_shared< stream_protocol::acceptor >( *m_io_service, stream_protocol::endpoint( location ) );
m_ipc_acceptor->set_option( socket_base::reuse_address( m_settings->get_reuse_address( ) ) );
m_ipc_acceptor->listen( m_settings->get_connection_limit( ) );
ipc_listen( );
log( Logger::INFO, String::format( "Service accepting HTTP connections at '%s'.", location.data( ) ) );
}
void ServiceImpl::ipc_listen( void ) const
{
auto socket = make_shared< stream_protocol::socket >( *m_io_service );
m_ipc_acceptor->async_accept( *socket, bind( &ServiceImpl::create_ipc_session, this, socket, _1 ) );
}
void ServiceImpl::create_ipc_session( const shared_ptr< stream_protocol::socket >& socket, const error_code& error ) const
{
if ( not error )
{
auto connection = make_shared< IPCSocketImpl >( *m_io_service, socket, m_logger );
connection->set_timeout( m_settings->get_connection_timeout( ) );
if (m_settings->get_keep_alive()) {
connection->set_keep_alive( m_settings->get_keep_alive_start(),
m_settings->get_keep_alive_interval(),
m_settings->get_keep_alive_cnt());
}
m_session_manager->create( [ this, connection ]( const shared_ptr< Session > session )
{
session->m_pimpl->m_settings = m_settings;
session->m_pimpl->m_manager = m_session_manager;
session->m_pimpl->m_web_socket_manager = m_web_socket_manager;
session->m_pimpl->m_error_handler = m_error_handler;
session->m_pimpl->m_request = make_shared< Request >( );
session->m_pimpl->m_request->m_pimpl->m_socket = connection;
session->m_pimpl->m_request->m_pimpl->m_socket->m_error_handler = m_error_handler;
session->m_pimpl->m_request->m_pimpl->m_buffer = make_shared< asio::streambuf >( );
session->m_pimpl->m_keep_alive_callback = bind( &ServiceImpl::parse_request, this, _1, _2, _3 );
session->m_pimpl->m_request->m_pimpl->m_socket->start_read( session->m_pimpl->m_request->m_pimpl->m_buffer, "\r\n\r\n", bind( &ServiceImpl::parse_request, this, _1, _2, session ) );
} );
}
else
{
if ( socket not_eq nullptr and socket->is_open( ) )
{
socket->close( );
}
log( Logger::WARNING, String::format( "Failed to create session, '%s'.", error.message( ).data( ) ) );
}
ipc_listen( );
}
#endif
string ServiceImpl::sanitise_path( const string& path ) const
{
if ( path == "/" )
{
return path;
}
smatch matches;
string sanitised_path = "";
static const regex pattern( "^\\{[a-zA-Z0-9_\\-]+: ?(.*)\\}$" );
for ( auto folder : String::split( path, '/' ) )
{
if ( folder.front( ) == '{' and folder.back( ) == '}' )
{
if ( not regex_match( folder, matches, pattern ) or matches.size( ) not_eq 2 )
{
throw runtime_error( String::format( "Resource path parameter declaration is malformed '%s'.", folder.data( ) ) );
}
sanitised_path += '/' + matches[ 1 ].str( );
}
else
{
sanitised_path += '/' + folder;
}
}
if ( path.back( ) == '/' )
{
sanitised_path += '/';
}
return sanitised_path;
}
void ServiceImpl::not_found( const shared_ptr< Session > session ) const
{
log( Logger::INFO, String::format( "'%s' resource route not found '%s'.",
session->get_origin( ).data( ),
session->get_request( )->get_path( ).data( ) ) );
if ( m_not_found_handler not_eq nullptr )
{
m_not_found_handler( session );
}
else
{
session->close( NOT_FOUND );
}
}
bool ServiceImpl::has_unique_paths( const set< string >& paths ) const
{
if ( paths.empty( ) )
{
return false;
}
for ( const auto& path : paths )
{
if ( m_resource_routes.count( path ) )
{
return false;
}
}
return true;
}
void ServiceImpl::log( const Logger::Level level, const string& message ) const
{
if ( m_logger not_eq nullptr )
{
try
{
m_logger->log( level, "%s", message.data( ) );
}
catch ( ... )
{
fprintf( stderr, "Failed to create log entry: %s", message.data( ) );
}
}
}
void ServiceImpl::method_not_allowed( const shared_ptr< Session > session ) const
{
log( Logger::INFO, String::format( "'%s' '%s' method not allowed '%s'.",
session->get_origin( ).data( ),
session->get_request( )->get_method( ).data( ),
session->get_request( )->get_path( ).data( ) ) );
if ( m_method_not_allowed_handler not_eq nullptr )
{
m_method_not_allowed_handler( session );
}
else
{
session->close( METHOD_NOT_ALLOWED );
}
}
void ServiceImpl::method_not_implemented( const shared_ptr< Session > session ) const
{
log( Logger::INFO, String::format( "'%s' '%s' method not implemented '%s'.",
session->get_origin( ).data( ),
session->get_request( )->get_method( ).data( ),
session->get_request( )->get_path( ).data( ) ) );
if ( m_method_not_implemented_handler not_eq nullptr )
{
m_method_not_implemented_handler( session );
}
else
{
session->close( NOT_IMPLEMENTED );
}
}
void ServiceImpl::failed_filter_validation( const shared_ptr< Session > session ) const
{
log( Logger::INFO, String::format( "'%s' failed filter validation '%s'.",
session->get_origin( ).data( ),
session->get_request( )->get_path( ).data( ) ) );
if ( m_failed_filter_validation_handler not_eq nullptr )
{
m_failed_filter_validation_handler( session );
}
else
{
session->close( BAD_REQUEST, { { "Connection", "close" } } );
}
}
void ServiceImpl::router( const shared_ptr< Session > session ) const
{
log( Logger::INFO, String::format( "Incoming '%s' request from '%s' for route '%s'.",
session->get_request( )->get_method( ).data( ),
session->get_origin( ).data( ),
session->get_request( )->get_path( ).data( ) ) );
if ( session->is_closed( ) )
{
return;
}
rule_engine( session, m_rules, [ this ]( const shared_ptr< Session > session )
{
const auto resource_route = find_if( m_resource_routes.begin( ), m_resource_routes.end( ), bind( &ServiceImpl::resource_router, this, session, _1 ) );
if ( resource_route == m_resource_routes.end( ) )
{
return not_found( session );
}
const auto path = resource_route->first;
session->m_pimpl->m_resource = resource_route->second;
const auto request = session->get_request( );
extract_path_parameters( path, request );
const auto callback = [ this ]( const shared_ptr< Session > session )
{
rule_engine( session, session->m_pimpl->m_resource->m_pimpl->m_rules, [ this ]( const shared_ptr< Session > session )
{
if ( session->is_closed( ) )
{
return;
}
const auto request = session->get_request( );
auto method_handler = find_method_handler( session );
if ( method_handler == nullptr )
{
if ( m_supported_methods.count( request->get_method( ) ) == 0 )
{
method_handler = bind( &ServiceImpl::method_not_implemented, this, _1 );
}
else
{
method_handler = bind( &ServiceImpl::method_not_allowed, this, _1 );
}
}
method_handler( session );
} );
};
if ( session->m_pimpl->m_resource->m_pimpl->m_authentication_handler not_eq nullptr )
{
session->m_pimpl->m_resource->m_pimpl->m_authentication_handler( session, callback );
}
else
{
callback( session );
}
} );
}
void ServiceImpl::create_session( const shared_ptr< tcp::socket >& socket, const error_code& error ) const
{
if ( not error )
{
auto connection = make_shared< SocketImpl >( *m_io_service, socket, m_logger );
connection->set_timeout( m_settings->get_connection_timeout( ) );
if (m_settings->get_keep_alive()) {
connection->set_keep_alive( m_settings->get_keep_alive_start(),
m_settings->get_keep_alive_interval(),
m_settings->get_keep_alive_cnt());
}
m_session_manager->create( [ this, connection ]( const shared_ptr< Session > session )
{
session->m_pimpl->m_settings = m_settings;
session->m_pimpl->m_manager = m_session_manager;
session->m_pimpl->m_web_socket_manager = m_web_socket_manager;
session->m_pimpl->m_error_handler = m_error_handler;
session->m_pimpl->m_request = make_shared< Request >( );
session->m_pimpl->m_request->m_pimpl->m_socket = connection;
session->m_pimpl->m_request->m_pimpl->m_socket->m_error_handler = m_error_handler;
session->m_pimpl->m_request->m_pimpl->m_buffer = make_shared< asio::streambuf >( );
session->m_pimpl->m_keep_alive_callback = bind( &ServiceImpl::parse_request, this, _1, _2, _3 );
session->m_pimpl->m_request->m_pimpl->m_socket->start_read( session->m_pimpl->m_request->m_pimpl->m_buffer, "\r\n\r\n", bind( &ServiceImpl::parse_request, this, _1, _2, session ) );
} );
}
else
{
if ( socket not_eq nullptr and socket->is_open( ) )
{
socket->close( );
}
log( Logger::WARNING, String::format( "Failed to create session, '%s'.", error.message( ).data( ) ) );
}
http_listen( );
}
void ServiceImpl::extract_path_parameters( const string& sanitised_path, const shared_ptr< const Request >& request ) const
{
smatch matches;
static const regex pattern( "^\\{([a-zA-Z0-9_\\-]+): ?.*\\}$" );
const auto folders = String::split( request->get_path( ), '/' );
const auto declarations = String::split( m_settings->get_root( ) + "/" + m_resource_paths.at( sanitised_path ), '/' );
// Clear previous parameters in case of "keep-alive" request
request->m_pimpl->m_path_parameters.clear();
for ( size_t index = 0; index < folders.size( ) and index < declarations.size( ); index++ )
{
const auto declaration = declarations[ index ];
if ( declaration.front( ) == '{' and declaration.back( ) == '}' )
{
regex_match( declaration, matches, pattern );
request->m_pimpl->m_path_parameters.insert( make_pair( matches[ 1 ].str( ), folders[ index ] ) );
}
}
}
function< void ( const shared_ptr< Session > ) > ServiceImpl::find_method_handler( const shared_ptr< Session > session ) const
{
const auto request = session->get_request( );
const auto resource = session->get_resource( );
const auto method_handlers = resource->m_pimpl->m_method_handlers.equal_range( request->get_method( ) );
bool failed_filter_validation = false;
function< void ( const shared_ptr< Session > ) > method_handler = nullptr;
for ( auto handler = method_handlers.first; handler not_eq method_handlers.second and method_handler == nullptr; handler++ )
{
method_handler = handler->second.second;
for ( const auto& filter : handler->second.first )
{
for ( const auto& header : request->get_headers( filter.first ) )
{
if ( not regex_match( header.second, regex( filter.second ) ) )
{
method_handler = nullptr;
failed_filter_validation = true;
}
}
}
}
if ( failed_filter_validation and method_handler == nullptr )
{
const auto handler = resource->m_pimpl->m_failed_filter_validation_handler;
method_handler = ( handler == nullptr ) ? bind( &ServiceImpl::failed_filter_validation, this, _1 ) : handler;
}
return method_handler;
}
void ServiceImpl::authenticate( const shared_ptr< Session > session ) const
{
if ( m_authentication_handler not_eq nullptr )
{
m_authentication_handler( session, [ this ]( const shared_ptr< Session > session )
{
m_session_manager->load( session, bind( &ServiceImpl::router, this, _1 ) );
} );
}
else
{
m_session_manager->load( session, bind( &ServiceImpl::router, this, _1 ) );
}
}
bool ServiceImpl::resource_router( const shared_ptr< Session > session, const pair< string, shared_ptr< const Resource > >& route ) const
{
const auto request = session->get_request( );
const auto path_folders = String::split( request->get_path( ), '/' );
const auto route_folders = String::split( m_settings->get_root( ) + "/" + route.first, '/' );
if ( path_folders.empty( ) and route_folders.empty( ) )
{
return true;
}
bool match = false;
if ( path_folders.size( ) == route_folders.size( ) )
{
for ( size_t index = 0; index < path_folders.size( ); index++ )
{
if ( m_settings->get_case_insensitive_uris( ) )
{
match = regex_match( path_folders[ index ], regex( route_folders[ index ], icase ) );
}
else
{
match = regex_match( path_folders[ index ], regex( route_folders[ index ] ) );
}
if ( not match )
{
break;
}
}
}
return match;
}
void ServiceImpl::default_error_handler( const int status, const exception& error, const shared_ptr< Session > session )
{
if ( session not_eq nullptr and session->is_open( ) )
{
string body = error.what( );
session->close( status, body, { { "Content-Type", "text/plain" }, { "Content-Length", ::to_string( body.length( ) ) } } );
}
}
void ServiceImpl::discard_request( istream& stream )
{
string line = String::empty;
while ( getline( stream, line ) )
{
if ( line == "\r" )
{
break;
}
}
}
const map< string, string > ServiceImpl::parse_request_line( istream& stream )
{
smatch matches;
static const regex pattern( "^([0-9a-zA-Z]*) ([a-zA-Z0-9:@_~!,;=#%&'\\-\\.\\/\\?\\$\\(\\)\\*\\+]+) (HTTP\\/[0-9]\\.[0-9])\\s*$" );
string data = "";
getline( stream, data );
if ( not regex_match( data, matches, pattern ) or matches.size( ) not_eq 4 )
{
throw runtime_error( "Your client has issued a malformed or illegal request status line. Thats all we know." );
}
const string protocol = matches[ 3 ].str( );
const auto delimiter = protocol.find_first_of( "/" );
return map< string, string >
{
{ "path", matches[ 2 ].str( ) },
{ "method", matches[ 1 ].str( ) },
{ "version", protocol.substr( delimiter + 1 ) },
{ "protocol", protocol.substr( 0, delimiter ) }
};
}
const multimap< string, string > ServiceImpl::parse_request_headers( istream& stream )
{
smatch matches;
string data = "";
multimap< string, string > headers;
static const regex pattern( "^([^:.]*): *(.*)\\s*$" );
while ( getline( stream, data ) and data not_eq "\r" )
{
if ( not regex_match( data, matches, pattern ) or matches.size( ) not_eq 3 )
{
throw runtime_error( "Your client has issued a malformed or illegal request header. Thats all we know." );
}
headers.insert( make_pair( matches[ 1 ].str( ), matches[ 2 ].str( ) ) );
}
return headers;
}
void ServiceImpl::parse_request( const error_code& error, size_t, const shared_ptr< Session > session ) const
{
istream stream( session->m_pimpl->m_request->m_pimpl->m_buffer.get( ) );
if ( error )
{
discard_request( stream );
const auto error_handler = get_error_handler( session );
return error_handler( 400, runtime_error( error.message( ) ), session );
}
try
{
const auto items = parse_request_line( stream );
const auto uri = Uri::parse( "http://localhost" + items.at( "path" ) );
session->m_pimpl->m_request->m_pimpl->m_body.clear( );
session->m_pimpl->m_request->m_pimpl->m_path = Uri::decode( uri.get_path( ) );
session->m_pimpl->m_request->m_pimpl->m_method = items.at( "method" );
session->m_pimpl->m_request->m_pimpl->m_headers = parse_request_headers( stream );
session->m_pimpl->m_request->m_pimpl->m_query_parameters = uri.get_query_parameters( );
char* locale = strdup( setlocale( LC_NUMERIC, nullptr ) );
setlocale( LC_NUMERIC, "C" );
session->m_pimpl->m_request->m_pimpl->m_version = stod( items.at( "version" ) );
setlocale( LC_NUMERIC, locale );
free( locale );
authenticate( session );
}
catch ( const int status_code )
{
discard_request( stream );
const auto error_handler = get_error_handler( session );
error_handler( status_code, runtime_error( m_settings->get_status_message( status_code ) ), session );
}
catch ( const regex_error& re )
{
discard_request( stream );
const auto error_handler = get_error_handler( session );
error_handler( 500, re, session );
}
catch ( const runtime_error& re )
{
discard_request( stream );
const auto error_handler = get_error_handler( session );
error_handler( 400, re, session );
}
catch ( const exception& ex )
{
discard_request( stream );
const auto error_handler = get_error_handler( session );
error_handler( 500, ex, session );
}
catch ( ... )
{
discard_request( stream );
auto cex = current_exception( );
if ( cex not_eq nullptr )
{
try
{
rethrow_exception( cex );
}
catch ( const exception& ex )
{
const auto error_handler = get_error_handler( session );
error_handler( 500, ex, session );
}
catch ( ... )
{
const auto error_handler = get_error_handler( session );
error_handler( 500, runtime_error( "Internal Server Error" ), session );
}
}
else
{
const auto error_handler = get_error_handler( session );
error_handler( 500, runtime_error( "Internal Server Error" ), session );
}
}
}
const shared_ptr< const Uri > ServiceImpl::get_http_uri( void ) const
{
if ( m_acceptor == nullptr )
{
return nullptr;
}
auto endpoint = m_acceptor->local_endpoint( );
auto address = endpoint.address( );
auto uri = String::empty;
if ( address.is_v6( ) )
{
uri = String::format( "http://[%s]:%u", address.to_string( ).data( ), endpoint.port( ) );
}
else
{
uri = String::format( "http://%s:%u", address.to_string( ).data( ), endpoint.port( ) );
}
return make_shared< const Uri >( uri );
}
const shared_ptr< const Uri > ServiceImpl::get_https_uri( void ) const
{
#ifdef BUILD_SSL
if ( m_ssl_acceptor == nullptr )
{
return nullptr;
}
auto endpoint = m_ssl_acceptor->local_endpoint( );
auto address = endpoint.address( );
auto uri = String::empty;
if ( address.is_v6( ) )
{
uri = String::format( "https://[%s]:%u", address.to_string( ).data( ), endpoint.port( ) );
}
else
{
uri = String::format( "https://%s:%u", address.to_string( ).data( ), endpoint.port( ) );
}
return make_shared< const Uri >( uri );
#else
throw runtime_error( "Not Implemented! Rebuild Restbed with SSL funcationality enabled." );
#endif
}
const function< void ( const int, const exception&, const shared_ptr< Session > ) > ServiceImpl::get_error_handler( const shared_ptr< Session >& session ) const
{
return ( session->m_pimpl->m_resource not_eq nullptr and session->m_pimpl->m_resource->m_pimpl->m_error_handler not_eq nullptr ) ? session->m_pimpl->m_resource->m_pimpl->m_error_handler : m_error_handler;
}
}
}

View File

@@ -0,0 +1,230 @@
/*
* Copyright 2013-2020, Corvusoft Ltd, All Rights Reserved.
*/
#pragma once
//System Includes
#include <set>
#include <map>
#include <chrono>
#include <future>
#include <memory>
#include <string>
#include <vector>
#include <stdexcept>
#include <functional>
#include <system_error>
//Project Includes
//External Includes
#include <asio/ip/tcp.hpp>
#include <asio/signal_set.hpp>
#include <asio/io_service.hpp>
#ifdef BUILD_SSL
#include <asio/ssl.hpp>
#endif
#ifdef BUILD_IPC
#include <asio/local/stream_protocol.hpp>
#endif
//System Namespaces
//Project Namespaces
//External Namespaces
namespace restbed
{
//Forward Declarations
class Uri;
class Rule;
class Logger;
class Session;
class Resource;
class Settings;
class SessionManager;
class SSLSettings;
namespace detail
{
//Forward Declarations
class WebSocketManagerImpl;
class ServiceImpl
{
public:
//Friends
//Definitions
//Constructors
ServiceImpl( void );
virtual ~ServiceImpl( void );
//Functionality
void http_start( void );
void http_listen( void ) const;
#ifdef BUILD_SSL
void https_start( void );
void https_listen( void ) const;
void create_ssl_session( const std::shared_ptr< asio::ssl::stream< asio::ip::tcp::socket > >& socket, const std::error_code& error ) const;
#endif
#ifdef BUILD_IPC
void ipc_start( void );
void ipc_listen( void ) const;
void create_ipc_session( const std::shared_ptr< asio::local::stream_protocol::socket >& socket, const std::error_code& error ) const;
#endif
void setup_signal_handler( void );
void signal_handler( const std::error_code& error, const int signal_number ) const;
std::string sanitise_path( const std::string& path ) const;
void not_found( const std::shared_ptr< Session > session ) const;
bool has_unique_paths( const std::set< std::string >& paths ) const;
void log( const Logger::Level level, const std::string& message ) const;
void method_not_allowed( const std::shared_ptr< Session > session ) const;
void method_not_implemented( const std::shared_ptr< Session > session ) const;
void failed_filter_validation( const std::shared_ptr< Session > session ) const;
void router( const std::shared_ptr< Session > session ) const;
void create_session( const std::shared_ptr< asio::ip::tcp::socket >& socket, const std::error_code& error ) const;
void extract_path_parameters( const std::string& sanitised_path, const std::shared_ptr< const Request >& request ) const;
std::function< void ( const std::shared_ptr< Session > ) > find_method_handler( const std::shared_ptr< Session > session ) const;
void authenticate( const std::shared_ptr< Session > session ) const;
bool resource_router( const std::shared_ptr< Session > session, const std::pair< std::string, std::shared_ptr< const Resource > >& route ) const;
static void default_error_handler( const int status, const std::exception& error, const std::shared_ptr< Session > session );
static void discard_request( std::istream& stream );
static const std::map< std::string, std::string > parse_request_line( std::istream& stream );
static const std::multimap< std::string, std::string > parse_request_headers( std::istream& stream );
void parse_request( const std::error_code& error, std::size_t length, const std::shared_ptr< Session > session ) const;
//Getters
const std::shared_ptr< const Uri > get_http_uri( void ) const;
const std::shared_ptr< const Uri > get_https_uri( void ) const;
const std::function< void ( const int, const std::exception&, const std::shared_ptr< Session > ) > get_error_handler( const std::shared_ptr< Session >& session ) const;
//Setters
//Operators
//Properties
std::chrono::steady_clock::time_point m_uptime;
std::shared_ptr< Logger > m_logger;
std::set< std::string > m_supported_methods;
std::shared_ptr< const Settings > m_settings;
std::shared_ptr< asio::io_service > m_io_service;
std::shared_ptr< asio::signal_set > m_signal_set;
std::shared_ptr< SessionManager > m_session_manager;
std::shared_ptr< WebSocketManagerImpl > m_web_socket_manager;
std::vector< std::shared_ptr< Rule > > m_rules;
std::unique_ptr< std::future<void > > m_workers_stopped;
#ifdef BUILD_SSL
std::shared_ptr< const SSLSettings > m_ssl_settings;
std::shared_ptr< asio::ssl::context > m_ssl_context;
std::shared_ptr< asio::ip::tcp::acceptor > m_ssl_acceptor;
#endif
#ifdef BUILD_IPC
std::shared_ptr< asio::local::stream_protocol::acceptor > m_ipc_acceptor;
#endif
std::shared_ptr< asio::ip::tcp::acceptor > m_acceptor;
std::map< std::string, std::string > m_resource_paths;
std::map< std::string, std::shared_ptr< const Resource > > m_resource_routes;
std::function< void ( void ) > m_ready_handler;
std::map< int, std::function< void ( const int ) > > m_signal_handlers;
std::function< void ( const std::shared_ptr< Session > ) > m_not_found_handler;
std::function< void ( const std::shared_ptr< Session > ) > m_method_not_allowed_handler;
std::function< void ( const std::shared_ptr< Session > ) > m_method_not_implemented_handler;
std::function< void ( const std::shared_ptr< Session > ) > m_failed_filter_validation_handler;
std::function< void ( const int, const std::exception&, const std::shared_ptr< Session > ) > m_error_handler;
std::function< void ( const std::shared_ptr< Session >, const std::function< void ( const std::shared_ptr< Session > ) >& ) > m_authentication_handler;
protected:
//Friends
//Definitions
//Constructors
//Functionality
//Getters
//Setters
//Operators
//Properties
private:
//Friends
//Definitions
//Constructors
ServiceImpl( const ServiceImpl& original ) = delete;
//Functionality
//Getters
//Setters
//Operators
ServiceImpl& operator =( const ServiceImpl& value ) = delete;
//Properties
};
}
}

View File

@@ -0,0 +1,212 @@
/*
* Copyright 2013-2020, Corvusoft Ltd, All Rights Reserved.
*/
//System Includes
#include <regex>
#include <utility>
#include <ciso646>
#include <stdexcept>
#include <system_error>
//Project Includes
#include "corvusoft/restbed/uri.hpp"
#include "corvusoft/restbed/http.hpp"
#include "corvusoft/restbed/string.hpp"
#include "corvusoft/restbed/session.hpp"
#include "corvusoft/restbed/request.hpp"
#include "corvusoft/restbed/response.hpp"
#include "corvusoft/restbed/resource.hpp"
#include "corvusoft/restbed/settings.hpp"
#include "corvusoft/restbed/context_value.hpp"
#include "corvusoft/restbed/session_manager.hpp"
#include "corvusoft/restbed/detail/socket_impl.hpp"
#include "corvusoft/restbed/detail/request_impl.hpp"
#include "corvusoft/restbed/detail/session_impl.hpp"
#include "corvusoft/restbed/detail/resource_impl.hpp"
//External Includes
//System Namespaces
using std::map;
using std::set;
using std::regex;
using std::smatch;
using std::string;
using std::getline;
using std::istream;
using std::function;
using std::multimap;
using std::make_pair;
using std::exception;
using std::to_string;
using std::ssub_match;
using std::shared_ptr;
using std::error_code;
using std::make_shared;
using std::regex_match;
using std::regex_error;
using std::runtime_error;
using std::placeholders::_1;
using std::rethrow_exception;
using std::current_exception;
using std::chrono::milliseconds;
//Project Namespaces
using restbed::detail::SessionImpl;
//External Namespaces
using asio::buffer;
using asio::streambuf;
namespace restbed
{
namespace detail
{
SessionImpl::SessionImpl( void ) : m_id( String::empty ),
m_request( nullptr ),
m_resource( nullptr ),
m_settings( nullptr ),
m_manager( nullptr ),
m_web_socket_manager( nullptr ),
m_headers( ),
m_context( ),
m_error_handler( nullptr ),
m_keep_alive_callback( nullptr ),
m_error_handler_invoked( false )
{
return;
}
SessionImpl::~SessionImpl( void )
{
return;
}
void SessionImpl::fetch_body( const size_t length, const shared_ptr< Session > session, const function< void ( const shared_ptr< Session >, const Bytes& ) >& callback ) const
{
const auto data_ptr = asio::buffer_cast< const Byte* >( session->m_pimpl->m_request->m_pimpl->m_buffer->data( ) );
const auto data = Bytes( data_ptr, data_ptr + length );
session->m_pimpl->m_request->m_pimpl->m_buffer->consume( length );
auto& body = m_request->m_pimpl->m_body;
if ( body.empty( ) )
{
body = data;
}
else
{
body.insert( body.end( ), data.begin( ), data.end( ) );
}
try
{
callback(session, data);
}
catch ( const int status_code )
{
const auto error_handler = session->m_pimpl->get_error_handler();
error_handler( status_code, runtime_error( m_settings->get_status_message( status_code ) ), session );
}
catch ( const regex_error& re )
{
const auto error_handler = session->m_pimpl->get_error_handler();
error_handler( 500, re, session );
}
catch ( const runtime_error& re )
{
const auto error_handler = session->m_pimpl->get_error_handler();
error_handler( 400, re, session );
}
catch ( const exception& ex )
{
const auto error_handler = session->m_pimpl->get_error_handler();
error_handler( 500, ex, session );
}
catch ( ... )
{
auto cex = current_exception( );
if ( cex not_eq nullptr )
{
try
{
rethrow_exception( cex );
}
catch ( const exception& ex )
{
const auto error_handler = session->m_pimpl->get_error_handler();
error_handler( 500, ex, session );
}
catch ( ... )
{
const auto error_handler = session->m_pimpl->get_error_handler();
error_handler( 500, runtime_error( "Internal Server Error" ), session );
}
}
else
{
const auto error_handler = session->m_pimpl->get_error_handler();
error_handler( 500, runtime_error( "Internal Server Error" ), session );
}
}
}
void SessionImpl::transmit( const Response& response, const function< void ( const error_code&, size_t ) >& callback ) const
{
auto hdrs = m_settings->get_default_headers( );
if ( m_resource not_eq nullptr )
{
const auto m_resource_headers = m_resource->m_pimpl->m_default_headers;
hdrs.insert( m_resource_headers.begin( ), m_resource_headers.end( ) );
}
hdrs.insert( m_headers.begin( ), m_headers.end( ) );
auto response_headers = response.get_headers( );
hdrs.insert( response_headers.begin( ), response_headers.end( ) );
auto payload = make_shared< Response >( );
payload->set_headers( hdrs );
payload->set_body( response.get_body( ) );
payload->set_version( response.get_version( ) );
payload->set_protocol( response.get_protocol( ) );
payload->set_status_code( response.get_status_code( ) );
payload->set_status_message( response.get_status_message( ) );
if ( payload->get_status_message( ).empty( ) )
{
payload->set_status_message( m_settings->get_status_message( payload->get_status_code( ) ) );
}
m_request->m_pimpl->m_socket->start_write( Http::to_bytes( payload ), callback );
}
const function< void ( const int, const exception&, const shared_ptr< Session > ) > SessionImpl::get_error_handler( void )
{
if ( m_error_handler_invoked )
{
return [ ]( const int, const exception&, const shared_ptr< Session > ) { };
}
m_error_handler_invoked = true;
auto error_handler = ( m_resource not_eq nullptr and m_resource->m_pimpl->m_error_handler not_eq nullptr ) ? m_resource->m_pimpl->m_error_handler : m_error_handler;
if ( error_handler == nullptr )
{
return [ ]( const int, const exception&, const shared_ptr< Session > session )
{
if ( session not_eq nullptr and session->is_open( ) )
{
session->close( );
}
};
}
return error_handler;
}
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright 2013-2020, Corvusoft Ltd, All Rights Reserved.
*/
#pragma once
//System Includes
#include <map>
#include <string>
#include <memory>
#include <istream>
#include <functional>
#include <system_error>
//Project Includes
#include "corvusoft/restbed/byte.hpp"
//External Includes
//System Namespaces
//Project Namespaces
//External Namespaces
namespace restbed
{
//Forward Declarations
class Session;
class Request;
class Response;
class Resource;
class Settings;
class SessionManager;
namespace detail
{
//Forward Declarations
class WebSocketManagerImpl;
class SessionImpl
{
public:
//Friends
//Definitions
//Constructors
SessionImpl( void );
SessionImpl( const SessionImpl& original ) = delete;
virtual ~SessionImpl( void );
//Functionality
void fetch_body( const std::size_t length, const std::shared_ptr< Session > session, const std::function< void ( const std::shared_ptr< Session >, const Bytes& ) >& callback ) const;
void transmit( const Response& response, const std::function< void ( const std::error_code&, std::size_t ) >& callback ) const;
//Getters
const std::function< void ( const int, const std::exception&, const std::shared_ptr< Session > ) > get_error_handler( void );
//Setters
//Operators
SessionImpl& operator =( const SessionImpl& value ) = delete;
//Properties
std::string m_id;
std::shared_ptr< const Request > m_request;
std::shared_ptr< const Resource > m_resource;
std::shared_ptr< const Settings > m_settings;
std::shared_ptr< SessionManager > m_manager;
std::shared_ptr< WebSocketManagerImpl > m_web_socket_manager;
std::multimap< std::string, std::string > m_headers;
std::map< std::string, const ContextValue > m_context;
std::function< void ( const int, const std::exception&, const std::shared_ptr< Session > ) > m_error_handler;
std::function< void ( const std::error_code& error, std::size_t length, const std::shared_ptr< Session > ) > m_keep_alive_callback;
protected:
//Friends
//Definitions
//Constructors
//Functionality
//Getters
//Setters
//Operators
//Properties
private:
//Friends
//Definitions
//Constructors
//Functionality
//Getters
//Setters
//Operators
//Properties
bool m_error_handler_invoked;
};
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright 2013-2020, Corvusoft Ltd, All Rights Reserved.
*/
#pragma once
//System Includes
#include <map>
#include <string>
#include <memory>
#include <chrono>
#include <cstdint>
//Project Includes
//External Includes
//System Namespaces
//Project Namespaces
//External Namespaces
namespace restbed
{
//Forward Declarations
class SSLSettings;
namespace detail
{
//Forward Declarations
struct SettingsImpl
{
uint16_t m_port = 80;
std::string m_root = "/";
bool m_reuse_address = true;
unsigned int m_worker_limit = 0;
unsigned int m_connection_limit = 128;
std::string m_bind_address = "";
bool m_case_insensitive_uris = true;
bool m_keep_alive = true;
uint32_t m_keep_alive_start = 900;
uint32_t m_keep_alive_interval = 900;
uint32_t m_keep_alive_cnt = 3;
std::map< std::string, std::string > m_properties { };
std::shared_ptr< const SSLSettings > m_ssl_settings = nullptr;
std::multimap< std::string, std::string > m_default_headers { };
std::chrono::milliseconds m_connection_timeout = std::chrono::milliseconds( 5000 );
std::map< int, std::string > m_status_messages
{
{ 100, "Continue" },
{ 101, "Switching Protocols" },
{ 102, "Processing" },
{ 200, "OK" },
{ 201, "Created" },
{ 202, "Accepted" },
{ 203, "Non-Authoritative Information" },
{ 204, "No Content" },
{ 205, "Reset Content" },
{ 206, "Partial Content" },
{ 207, "Multi-Status" },
{ 208, "Already Reported" },
{ 226, "IM Used" },
{ 300, "Multiple Choices" },
{ 301, "Moved Permanently" },
{ 302, "Found" },
{ 303, "See Other" },
{ 304, "Not Modified" },
{ 305, "Use Proxy" },
{ 306, "Reserved" },
{ 307, "Temporary Redirect" },
{ 308, "Permanent Redirect" },
{ 400, "Bad Request" },
{ 401, "Unauthorized" },
{ 402, "Payment Required" },
{ 403, "Forbidden" },
{ 404, "Not Found" },
{ 405, "Method Not Allowed" },
{ 406, "Not Acceptable" },
{ 407, "Proxy Authentication Required" },
{ 408, "Request Timeout" },
{ 409, "Conflict" },
{ 410, "Gone" },
{ 411, "Length Required" },
{ 412, "Precondition Failed" },
{ 413, "Request Entity Too Large" },
{ 414, "Request URI Too Long" },
{ 415, "Unsupported Media Type" },
{ 416, "Requested Range Not Satisfiable" },
{ 417, "Expectation Failed" },
{ 422, "Unprocessable Entity" },
{ 423, "Locked" },
{ 424, "Failed Dependency" },
{ 426, "Upgrade Required" },
{ 428, "Precondition Required" },
{ 429, "Too Many Requests" },
{ 431, "Request Header Fields Too Large" },
{ 500, "Internal Server Error" },
{ 501, "Not Implemented" },
{ 502, "Bad Gateway" },
{ 503, "Service Unavailable" },
{ 504, "Gateway Timeout" },
{ 505, "HTTP Version Not Supported" },
{ 506, "Variant Also Negotiates" },
{ 507, "Insufficient Storage" },
{ 508, "Loop Detected" },
{ 510, "Not Extended" },
{ 511, "Network Authentication Required" }
};
};
}
}

View File

@@ -0,0 +1,728 @@
/*
* Copyright 2013-2020, Corvusoft Ltd, All Rights Reserved.
*/
//System Includes
#include <future>
#include <ciso646>
//Project Includes
#include "corvusoft/restbed/logger.hpp"
#include "corvusoft/restbed/detail/socket_impl.hpp"
//External Includes
#include <asio/read.hpp>
#include <asio/write.hpp>
#include <asio/connect.hpp>
#include <asio/read_until.hpp>
//System Namespaces
using std::get;
using std::bind;
using std::size_t;
using std::string;
using std::promise;
using std::function;
using std::to_string;
using std::error_code;
using std::shared_ptr;
using std::make_shared;
using std::runtime_error;
using std::placeholders::_1;
using std::chrono::milliseconds;
using std::chrono::steady_clock;
//Project Namespaces
using restbed::detail::SocketImpl;
//External Namespaces
using asio::ip::tcp;
using asio::io_service;
using asio::steady_timer;
#ifdef BUILD_SSL
using asio::ssl::stream;
#endif
namespace restbed
{
namespace detail
{
SocketImpl::SocketImpl( asio::io_context& context, const shared_ptr< tcp::socket >& socket, const shared_ptr< Logger >& logger ) : m_error_handler( nullptr ),
m_is_open( socket->is_open( ) ),
m_pending_writes( ),
m_logger( logger ),
m_timeout( 0 ),
m_io_service( context ),
m_timer( make_shared< asio::steady_timer >( m_io_service ) ),
m_strand( make_shared< io_service::strand > ( m_io_service ) ),
m_resolver( nullptr ),
m_socket( socket )
#ifdef BUILD_SSL
, m_ssl_socket( nullptr )
#endif
{
return;
}
#ifdef BUILD_SSL
SocketImpl::SocketImpl( asio::io_context& context, const shared_ptr< asio::ssl::stream< tcp::socket > >& socket, const shared_ptr< Logger >& logger ) : m_error_handler( nullptr ),
m_is_open( socket->lowest_layer( ).is_open( ) ),
m_pending_writes( ),
m_logger( logger ),
m_timeout( 0 ),
m_io_service( context ),
m_timer( make_shared< asio::steady_timer >( m_io_service ) ),
m_strand( make_shared< io_service::strand > ( m_io_service ) ),
m_resolver( nullptr ),
m_socket( nullptr ),
m_ssl_socket( socket )
{
return;
}
#endif
void SocketImpl::close( void )
{
m_is_open = false;
if ( m_timer not_eq nullptr )
{
m_timer->cancel( );
}
if ( m_socket not_eq nullptr )
{
m_socket->close( );
}
#ifdef BUILD_SSL
if ( m_ssl_socket not_eq nullptr )
{
m_ssl_socket->lowest_layer( ).close( );
}
#endif
}
bool SocketImpl::is_open( void ) const
{
return m_is_open;
}
bool SocketImpl::is_closed( void ) const
{
return not m_is_open;
}
void SocketImpl::connect( const string& hostname, const uint16_t port, const function< void ( const error_code& ) >& callback )
{
m_resolver = make_shared< tcp::resolver >( m_io_service );
tcp::resolver::query query( hostname, ::to_string( port ) );
m_resolver->async_resolve( query, [ this, callback ]( const error_code & error, tcp::resolver::iterator endpoint_iterator )
{
if ( not error )
{
#ifdef BUILD_SSL
auto& socket = ( m_socket not_eq nullptr ) ? *m_socket : m_ssl_socket->lowest_layer( );
#else
auto& socket = *m_socket;
#endif
asio::async_connect( socket, endpoint_iterator, [ this, callback ]( const error_code & error, tcp::resolver::iterator )
{
#ifdef BUILD_SSL
if ( m_ssl_socket not_eq nullptr )
{
m_ssl_socket->handshake( asio::ssl::stream_base::client );
}
#endif
m_is_open = true;
callback( error );
} );
}
else
{
callback( error );
}
} );
}
void SocketImpl::sleep_for( const milliseconds& delay, const function< void ( const error_code& ) >& callback )
{
m_timer->cancel( );
m_timer->expires_from_now( delay );
m_timer->async_wait( callback );
}
void SocketImpl::start_write(const Bytes& data, const std::function< void ( const std::error_code&, std::size_t ) >& callback)
{
m_strand->post([this, data, callback] { write_helper(data, callback); });
}
size_t SocketImpl::start_read(const shared_ptr< asio::streambuf >& data, const string& delimiter, error_code& error)
{
return read( data, delimiter,error );
}
size_t SocketImpl::start_read(const shared_ptr< asio::streambuf >& data, const size_t length, error_code& error)
{
return read( data, length, error );
}
void SocketImpl::start_read( const std::size_t length, const function< void ( const Bytes ) > success, const function< void ( const error_code ) > failure )
{
m_strand->post([this, length, success, failure] {
read(length, success, failure);
});
}
void SocketImpl::start_read(const shared_ptr< asio::streambuf >& data, const size_t length, const function< void ( const error_code&, size_t ) >& callback)
{
m_strand->post([this, data, length, callback]
{
read(data, length, callback);
});
}
void SocketImpl::start_read(const shared_ptr< asio::streambuf >& data, const string& delimiter, const function< void ( const error_code&, size_t ) >& callback)
{
m_strand->post([this, data, delimiter, callback]
{
read(data, delimiter, callback);
});
}
string SocketImpl::get_local_endpoint( void )
{
error_code error;
tcp::endpoint endpoint;
#ifdef BUILD_SSL
if ( m_socket not_eq nullptr )
{
#endif
endpoint = m_socket->local_endpoint( error );
#ifdef BUILD_SSL
}
else
{
endpoint = m_ssl_socket->lowest_layer( ).local_endpoint( error );
}
#endif
if ( error )
{
m_is_open = false;
}
auto address = endpoint.address( );
auto local = address.is_v4( ) ? address.to_string( ) + ":" : "[" + address.to_string( ) + "]:";
local += ::to_string( endpoint.port( ) );
return local;
}
string SocketImpl::get_remote_endpoint( void )
{
error_code error;
tcp::endpoint endpoint;
#ifdef BUILD_SSL
if ( m_socket not_eq nullptr )
{
#endif
endpoint = m_socket->remote_endpoint( error );
#ifdef BUILD_SSL
}
else
{
endpoint = m_ssl_socket->lowest_layer( ).remote_endpoint( error );
}
#endif
if ( error )
{
m_is_open = false;
}
auto address = endpoint.address( );
auto remote = address.is_v4( ) ? address.to_string( ) + ":" : "[" + address.to_string( ) + "]:";
remote += ::to_string( endpoint.port( ) );
return remote;
}
void SocketImpl::set_timeout( const milliseconds& value )
{
m_timeout = value;
}
void SocketImpl::set_keep_alive( const uint32_t start, const uint32_t interval, const uint32_t cnt)
{
(void) cnt;
(void) start;
(void) interval;
#ifdef BUILD_SSL
auto& socket = ( m_socket not_eq nullptr ) ? *m_socket : m_ssl_socket->lowest_layer( );
#else
auto& socket = *m_socket;
#endif
#ifdef _WIN32
std::string val = "1";
setsockopt(socket.native_handle(), SOL_SOCKET, SO_KEEPALIVE, val.c_str(), sizeof(val));
// TCP_KEEPIDLE and TCP_KEEPINTVL are available since Win 10 version 1709
// TCP_KEEPCNT since Win 10 version 1703
#ifdef TCP_KEEPIDLE
std::string start_str = std::to_string(start);
setsockopt(socket.native_handle(), IPPROTO_TCP, TCP_KEEPIDLE,
start_str.c_str(), sizeof(start_str));
#endif
#ifdef TCP_KEEPINTVL
std::string interval_str = std::to_string(interval);
setsockopt(socket.native_handle(), IPPROTO_TCP, TCP_KEEPINTVL,
interval_str.c_str(), sizeof(interval_str));
#endif
#ifdef TCP_KEEPCNT
std::string cnt_str = std::to_string(cnt);
setsockopt(socket.native_handle(), IPPROTO_TCP, TCP_KEEPCNT,
cnt_str.c_str(), sizeof(cnt_str));
#endif
#else
uint32_t val = 1;
setsockopt(socket.native_handle(), SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(uint32_t));
#ifdef __APPLE__
setsockopt(socket.native_handle(), IPPROTO_TCP, TCP_KEEPALIVE, &start, sizeof(uint32_t));
#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
setsockopt(socket.native_handle(), IPPROTO_TCP, SO_KEEPALIVE, &start, sizeof(uint32_t));
#else
// Linux based systems
setsockopt(socket.native_handle(), SOL_TCP, TCP_KEEPIDLE, &start, sizeof(uint32_t));
setsockopt(socket.native_handle(), SOL_TCP, TCP_KEEPINTVL, &interval, sizeof(uint32_t));
setsockopt(socket.native_handle(), SOL_TCP, TCP_KEEPCNT, &cnt, sizeof(uint32_t));
#endif
#endif
}
SocketImpl::SocketImpl( asio::io_context& context ) : m_error_handler( nullptr ),
m_is_open( false ),
m_pending_writes( ),
m_logger( nullptr ),
m_timeout( 0 ),
m_io_service( context ),
m_timer( make_shared< asio::steady_timer >( m_io_service ) ),
m_strand( make_shared< io_service::strand > ( m_io_service ) ),
m_resolver( nullptr ),
m_socket( nullptr )
#ifdef BUILD_SSL
, m_ssl_socket( nullptr )
#endif
{
return;
}
void SocketImpl::connection_timeout_handler( const shared_ptr< SocketImpl > socket, const error_code& error )
{
if ( error or socket == nullptr or socket->m_timer->expires_at( ) > steady_clock::now( ) )
{
return;
}
socket->close( );
if ( m_error_handler not_eq nullptr )
{
m_error_handler( 408, runtime_error( "The socket timed out waiting for the request." ), nullptr );
}
}
void SocketImpl::write( void )
{
if(m_is_open)
{
m_timer->cancel( );
m_timer->expires_from_now( m_timeout );
m_timer->async_wait( m_strand->wrap( bind( &SocketImpl::connection_timeout_handler, this, shared_from_this( ), _1 ) ) );
#ifdef BUILD_SSL
if ( m_socket not_eq nullptr )
{
#endif
asio::async_write( *m_socket, asio::buffer( get<0>(m_pending_writes.front()).data( ), get<0>(m_pending_writes.front()).size( ) ), m_strand->wrap( [ this ]( const error_code & error, size_t length )
{
m_timer->cancel( );
auto callback = get<2>(m_pending_writes.front());
auto & retries = get<1>(m_pending_writes.front());
auto & buffer = get<0>(m_pending_writes.front());
if(length < buffer.size() && retries < MAX_WRITE_RETRIES && error not_eq asio::error::operation_aborted)
{
++retries;
buffer.erase(buffer.begin(),buffer.begin() + length);
}
else
{
m_pending_writes.pop();
}
if ( error not_eq asio::error::operation_aborted )
{
callback( error, length );
}
if(!m_pending_writes.empty())
{
write();
}
} ) );
#ifdef BUILD_SSL
}
else
{
asio::async_write(*m_ssl_socket, asio::buffer( get<0>(m_pending_writes.front()).data( ), get<0>(m_pending_writes.front()).size( ) ), m_strand->wrap( [ this ]( const error_code & error, size_t length )
{
m_timer->cancel( );
auto callback = get<2>(m_pending_writes.front());
auto & retries = get<1>(m_pending_writes.front());
auto & buffer = get<0>(m_pending_writes.front());
if(length < buffer.size() && retries < MAX_WRITE_RETRIES && error not_eq asio::error::operation_aborted)
{
++retries;
buffer.erase(buffer.begin(),buffer.begin() + length);
}
else
{
m_pending_writes.pop();
}
if ( error not_eq asio::error::operation_aborted )
{
callback( error, length );
}
if(!m_pending_writes.empty())
{
write();
}
} ) );
}
#endif
}
else
{
while(!m_pending_writes.empty())
{
m_pending_writes.pop();
}
}
}
void SocketImpl::write( const Bytes& data, const function< void ( const error_code&, size_t ) >& callback )
{
const auto buffer = make_shared< Bytes >( data );
m_timer->cancel( );
m_timer->expires_from_now( m_timeout );
m_timer->async_wait( m_strand->wrap( bind( &SocketImpl::connection_timeout_handler, this, shared_from_this( ), _1 ) ) );
#ifdef BUILD_SSL
if ( m_socket not_eq nullptr )
{
#endif
asio::async_write( *m_socket, asio::buffer( buffer->data( ), buffer->size( ) ), m_strand->wrap( [ this, callback, buffer ]( const error_code & error, size_t length )
{
m_timer->cancel( );
if ( error )
{
m_is_open = false;
}
if ( error not_eq asio::error::operation_aborted )
{
callback( error, length );
}
} ) );
#ifdef BUILD_SSL
}
else
{
asio::async_write( *m_ssl_socket, asio::buffer( buffer->data( ), buffer->size( ) ), m_strand->wrap( [ this, callback, buffer ]( const error_code & error, size_t length )
{
m_timer->cancel( );
if ( error )
{
m_is_open = false;
}
if ( error not_eq asio::error::operation_aborted )
{
callback( error, length );
}
} ) );
}
#endif
}
void SocketImpl::write_helper(const Bytes& data, const function< void ( const error_code&, size_t ) >& callback)
{
const uint8_t retries = 0;
m_pending_writes.push(make_tuple(data, retries, callback));
if(m_pending_writes.size() == 1)
{
write();
}
}
size_t SocketImpl::read( const shared_ptr< asio::streambuf >& data, const size_t length, error_code& error )
{
m_timer->cancel( );
m_timer->expires_from_now( m_timeout );
m_timer->async_wait( m_strand->wrap( bind( &SocketImpl::connection_timeout_handler, this, shared_from_this( ), _1 ) ) );
size_t size = 0;
auto finished = std::make_shared<bool>(false);
auto sharedError = std::make_shared<error_code>();
auto sharedSize = std::make_shared<size_t>(0);
#ifdef BUILD_SSL
if ( m_socket not_eq nullptr )
{
#endif
asio::async_read( *m_socket, *data, asio::transfer_at_least( length ),
[ finished, sharedSize, sharedError ]( const error_code & error, size_t size ) {
*sharedError = error;
*sharedSize = size;
*finished = true;
});
#ifdef BUILD_SSL
}
else
{
asio::async_read( *m_ssl_socket, *data, asio::transfer_at_least( length ),
[ finished, sharedSize, sharedError ]( const error_code & error, size_t size ) {
*sharedError = error;
*sharedSize = size;
*finished = true;
});
}
#endif
while (!*finished)
m_io_service.run_one();
error = *sharedError;
size = *sharedSize;
m_timer->cancel( );
if ( error )
{
m_is_open = false;
}
return size;
}
void SocketImpl::read( const std::size_t length, const function< void ( const Bytes ) > success, const function< void ( const error_code ) > failure )
{
m_timer->cancel( );
m_timer->expires_from_now( m_timeout );
m_timer->async_wait( m_strand->wrap( bind( &SocketImpl::connection_timeout_handler, this, shared_from_this( ), _1 ) ) );
#ifdef BUILD_SSL
if ( m_socket not_eq nullptr )
{
#endif
auto data = make_shared< asio::streambuf >( );
asio::async_read( *m_socket, *data, asio::transfer_exactly( length ), [ this, data, success, failure ]( const error_code code, const size_t length )
{
m_timer->cancel( );
if ( code )
{
m_is_open = false;
failure( code );
}
else
{
const auto data_ptr = asio::buffer_cast< const Byte* >( data->data( ) );
success( Bytes( data_ptr, data_ptr + length ) );
}
} );
#ifdef BUILD_SSL
}
else
{
auto data = make_shared< asio::streambuf >( );
asio::async_read( *m_ssl_socket, *data, asio::transfer_exactly( length ), [ this, data, success, failure ]( const error_code code, const size_t length )
{
m_timer->cancel( );
if ( code )
{
m_is_open = false;
failure( code );
}
else
{
const auto data_ptr = asio::buffer_cast< const Byte* >( data->data( ) );
success( Bytes( data_ptr, data_ptr + length ) );
}
} );
}
#endif
}
void SocketImpl::read( const shared_ptr< asio::streambuf >& data, const size_t length, const function< void ( const error_code&, size_t ) >& callback )
{
m_timer->cancel( );
m_timer->expires_from_now( m_timeout );
m_timer->async_wait( m_strand->wrap( bind( &SocketImpl::connection_timeout_handler, this, shared_from_this( ), _1 ) ) );
#ifdef BUILD_SSL
if ( m_socket not_eq nullptr )
{
#endif
asio::async_read( *m_socket, *data, asio::transfer_at_least( length ), m_strand->wrap( [ this, callback ]( const error_code & error, size_t length )
{
m_timer->cancel( );
if ( error )
{
m_is_open = false;
}
if ( error not_eq asio::error::operation_aborted )
{
callback( error, length );
}
} ) );
#ifdef BUILD_SSL
}
else
{
asio::async_read( *m_ssl_socket, *data, asio::transfer_at_least( length ), m_strand->wrap( [ this, callback ]( const error_code & error, size_t length )
{
m_timer->cancel( );
if ( error )
{
m_is_open = false;
}
if ( error not_eq asio::error::operation_aborted )
{
callback( error, length );
}
} ) );
}
#endif
}
size_t SocketImpl::read( const shared_ptr< asio::streambuf >& data, const string& delimiter, error_code& error )
{
m_timer->cancel( );
m_timer->expires_from_now( m_timeout );
m_timer->async_wait( bind( &SocketImpl::connection_timeout_handler, this, shared_from_this( ), _1 ) );
size_t length = 0;
auto finished = std::make_shared<bool>(false);
auto sharedError = std::make_shared<error_code>();
auto sharedLength = std::make_shared<size_t>(0);
#ifdef BUILD_SSL
if ( m_socket not_eq nullptr )
{
#endif
asio::async_read_until( *m_socket, *data, delimiter,
[ finished, sharedLength, sharedError ]( const error_code & error, size_t length ) {
*sharedError = error;
*sharedLength = length;
*finished = true;
});
#ifdef BUILD_SSL
}
else
{
asio::async_read_until( *m_ssl_socket, *data, delimiter,
[ finished, sharedLength, sharedError ]( const error_code & error, size_t length ) {
*sharedError = error;
*sharedLength = length;
*finished = true;
});
}
#endif
while (!*finished)
m_io_service.run_one();
error = *sharedError;
length = *sharedLength;
m_timer->cancel( );
if ( error )
{
m_is_open = false;
}
return length;
}
void SocketImpl::read( const shared_ptr< asio::streambuf >& data, const string& delimiter, const function< void ( const error_code&, size_t ) >& callback )
{
m_timer->cancel( );
m_timer->expires_from_now( m_timeout );
m_timer->async_wait( m_strand->wrap( bind( &SocketImpl::connection_timeout_handler, this, shared_from_this( ), _1 ) ) );
#ifdef BUILD_SSL
if ( m_socket not_eq nullptr )
{
#endif
asio::async_read_until( *m_socket, *data, delimiter, m_strand->wrap( [ this, callback ]( const error_code & error, size_t length )
{
m_timer->cancel( );
if ( error )
{
m_is_open = false;
}
if ( error not_eq asio::error::operation_aborted )
{
callback( error, length );
}
} ) );
#ifdef BUILD_SSL
}
else
{
asio::async_read_until( *m_ssl_socket, *data, delimiter, m_strand->wrap( [ this, callback ]( const error_code & error, size_t length )
{
m_timer->cancel( );
if ( error )
{
m_is_open = false;
}
if ( error not_eq asio::error::operation_aborted )
{
callback( error, length );
}
} ) );
}
#endif
}
}
}

View File

@@ -0,0 +1,178 @@
/*
* Copyright 2013-2020, Corvusoft Ltd, All Rights Reserved.
*/
#pragma once
//System Includes
#include <queue>
#include <tuple>
#include <chrono>
#include <string>
#include <memory>
#include <cstdint>
#include <stdexcept>
#include <functional>
#include <system_error>
//Project Includes
#include "corvusoft/restbed/byte.hpp"
//External Includes
#include <asio/ip/tcp.hpp>
#include <asio/streambuf.hpp>
#include <asio/steady_timer.hpp>
#include <asio/io_service.hpp>
#include <asio/io_service_strand.hpp>
#ifdef BUILD_SSL
#include <asio/ssl.hpp>
#endif
//System Namespaces
//Project Namespaces
//External Namespaces
namespace restbed
{
//Forward Declarations
class Logger;
class Session;
namespace detail
{
//Forward Declarations
class SocketImpl : public std::enable_shared_from_this<SocketImpl>
{
public:
//Friends
//Definitions
//Constructors
SocketImpl( asio::io_context& context, const std::shared_ptr< asio::ip::tcp::socket >& socket, const std::shared_ptr< Logger >& logger = nullptr );
#ifdef BUILD_SSL
SocketImpl( asio::io_context& context, const std::shared_ptr< asio::ssl::stream< asio::ip::tcp::socket > >& socket, const std::shared_ptr< Logger >& logger = nullptr );
#endif
~SocketImpl( void ) = default;
//Functionality
virtual void close( void );
virtual bool is_open( void ) const;
virtual bool is_closed( void ) const;
virtual void connect( const std::string& hostname, const uint16_t port, const std::function< void ( const std::error_code& ) >& callback );
virtual void sleep_for( const std::chrono::milliseconds& delay, const std::function< void ( const std::error_code& ) >& callback );
virtual void start_write(const Bytes& data, const std::function< void ( const std::error_code&, std::size_t ) >& callback);
virtual size_t start_read( const std::shared_ptr< asio::streambuf >& data, const std::string& delimiter, std::error_code& error );
virtual size_t start_read( const std::shared_ptr< asio::streambuf >& data, const std::size_t length, std::error_code& error );
virtual void start_read(const std::size_t length, const std::function< void ( const Bytes ) > success, const std::function< void ( const std::error_code ) > failure );
virtual void start_read( const std::shared_ptr< asio::streambuf >& data, const std::size_t length, const std::function< void ( const std::error_code&, std::size_t ) >& callback );
virtual void start_read(const std::shared_ptr< asio::streambuf >& data, const std::string& delimiter, const std::function< void ( const std::error_code&, std::size_t ) >& callback );
//Getters
virtual std::string get_local_endpoint( void );
virtual std::string get_remote_endpoint( void );
//Setters
virtual void set_timeout( const std::chrono::milliseconds& value );
virtual void set_keep_alive( const uint32_t start, const uint32_t interval, const uint32_t cnt);
//Operators
//Properties
std::function< void ( const int, const std::exception&, const std::shared_ptr< Session > ) > m_error_handler;
protected:
//Friends
//Definitions
//Constructors
SocketImpl( asio::io_context& context );
//Functionality
//Getters
//Setters
//Operators
//Properties
private:
//Friends
//Definitions
//Constructors
SocketImpl( const SocketImpl& original ) = delete;
//Functionality
void connection_timeout_handler( const std::shared_ptr< SocketImpl > socket, const std::error_code& error );
void write( void );
void write( const Bytes& data, const std::function< void ( const std::error_code&, std::size_t ) >& callback );
void write_helper( const Bytes& data, const std::function< void ( const std::error_code&, std::size_t ) >& callback );
size_t read( const std::shared_ptr< asio::streambuf >& data, const std::size_t length, std::error_code& error );
void read( const std::size_t length, const std::function< void ( const Bytes ) > success, const std::function< void ( const std::error_code ) > failure );
void read( const std::shared_ptr< asio::streambuf >& data, const std::size_t length, const std::function< void ( const std::error_code&, std::size_t ) >& callback );
size_t read( const std::shared_ptr< asio::streambuf >& data, const std::string& delimiter, std::error_code& error );
void read( const std::shared_ptr< asio::streambuf >& data, const std::string& delimiter, const std::function< void ( const std::error_code&, std::size_t ) >& callback );
//Getters
//Setters
//Operators
SocketImpl& operator =( const SocketImpl& value ) = delete;
//Properties
bool m_is_open;
const uint8_t MAX_WRITE_RETRIES = 5;
std::queue< std::tuple< Bytes, uint8_t, std::function< void ( const std::error_code&, std::size_t ) > > > m_pending_writes;
std::shared_ptr< Logger > m_logger;
std::chrono::milliseconds m_timeout;
asio::io_context &m_io_service;
std::shared_ptr< asio::steady_timer > m_timer;
std::shared_ptr< asio::io_service::strand > m_strand;
std::shared_ptr< asio::ip::tcp::resolver > m_resolver;
std::shared_ptr< asio::ip::tcp::socket > m_socket;
#ifdef BUILD_SSL
std::shared_ptr< asio::ssl::stream< asio::ip::tcp::socket > > m_ssl_socket;
#endif
};
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2013-2020, Corvusoft Ltd, All Rights Reserved.
*/
#pragma once
//System Includes
#include <string>
#include <memory>
#include <cstdint>
//Project Includes
//External Includes
//System Namespaces
//Project Namespaces
//External Namespaces
namespace restbed
{
//Forward Declarations
namespace detail
{
//Forward Declarations
struct SSLSettingsImpl
{
uint16_t m_port = 443;
bool m_http_disabled = false;
bool m_sslv2_enabled = true;
bool m_sslv3_enabled = true;
bool m_tlsv1_enabled = true;
bool m_tlsv11_enabled = true;
bool m_tlsv12_enabled = true;
bool m_compression_enabled = true;
bool m_default_workarounds_enabled = true;
bool m_single_diffie_hellman_use_enabled = true;
std::string m_bind_address = "";
std::string m_passphrase = "";
std::string m_private_key = "";
std::string m_private_rsa_key = "";
std::string m_certificate = "";
std::string m_certificate_chain = "";
std::string m_certificate_authority_pool = "";
std::string m_temporary_diffie_hellman = "";
};
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2013-2020, Corvusoft Ltd, All Rights Reserved.
*/
#pragma once
//System Includes
#include <string>
//Project Includes
//External Includes
//System Namespaces
//Project Namespaces
//External Namespaces
namespace restbed
{
//Forward Declarations
namespace detail
{
//Forward Declarations
struct UriImpl
{
std::string m_uri = "";
bool m_relative = false;
};
}
}

View File

@@ -0,0 +1,138 @@
/*
* Copyright 2013-2020, Corvusoft Ltd, All Rights Reserved.
*/
//System Includes
#include <ciso646>
//Project Includes
#include "corvusoft/restbed/web_socket.hpp"
#include "corvusoft/restbed/web_socket_message.hpp"
#include "corvusoft/restbed/detail/socket_impl.hpp"
#include "corvusoft/restbed/detail/web_socket_impl.hpp"
#include "corvusoft/restbed/detail/web_socket_manager_impl.hpp"
//External Includes
//System Namespaces
using std::bind;
using std::string;
using std::shared_ptr;
using std::error_code;
using std::placeholders::_1;
//Project Namespaces
using restbed::detail::SocketImpl;
using restbed::detail::WebSocketImpl;
using restbed::detail::WebSocketManagerImpl;
//External Namespaces
namespace restbed
{
namespace detail
{
WebSocketImpl::WebSocketImpl( void )
{
return;
}
WebSocketImpl::~WebSocketImpl( void )
{
return;
}
void WebSocketImpl::log( const Logger::Level level, const string& message ) const
{
if ( m_logger not_eq nullptr )
{
try
{
m_logger->log( level, "%s", message.data( ) );
}
catch ( ... )
{
fprintf( stderr, "Failed to create log entry: %s", message.data( ) );
}
}
}
void WebSocketImpl::listen( const shared_ptr< WebSocket > socket )
{
m_socket->start_read( 2, bind( &WebSocketImpl::parse_flags, this, _1, socket ), [ this, socket ]( const error_code code )
{
if ( m_error_handler not_eq nullptr )
{
m_error_handler( socket, code );
}
} );
}
void WebSocketImpl::parse_flags( const Bytes data, const shared_ptr< WebSocket > socket )
{
auto message = m_manager->parse( data );
auto length = message->get_length( );
if ( length == 126 )
{
length = 2;
}
else if ( length == 127 )
{
length = 4;
}
else
{
length = 0;
}
if ( message->get_mask_flag( ) == true )
{
length += 4;
}
m_socket->start_read( length, bind( &WebSocketImpl::parse_length_and_mask, this, _1, data, socket ), [ this, socket ]( const error_code code )
{
if ( m_error_handler not_eq nullptr )
{
m_error_handler( socket, code );
}
} );
}
void WebSocketImpl::parse_payload( const Bytes data, Bytes packet, const shared_ptr< WebSocket > socket )
{
packet.insert( packet.end( ), data.begin( ), data.end( ) );
auto message = m_manager->parse( packet );
if ( m_message_handler not_eq nullptr )
{
m_message_handler( socket, message );
}
listen( socket );
}
void WebSocketImpl::parse_length_and_mask( const Bytes data, Bytes packet, const shared_ptr< WebSocket > socket )
{
packet.insert( packet.end( ), data.begin( ), data.end( ) );
auto message = m_manager->parse( packet );
auto length = message->get_extended_length( );
if ( length == 0 )
{
length = message->get_length( );
}
m_socket->start_read( length, bind( &WebSocketImpl::parse_payload, this, _1, packet, socket ), [ this, socket ]( const error_code code )
{
if ( m_error_handler not_eq nullptr )
{
m_error_handler( socket, code );
}
} );
}
}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2013-2020, Corvusoft Ltd, All Rights Reserved.
*/
#pragma once
//System Includes
#include <memory>
#include <string>
#include <functional>
//Project Includes
#include "corvusoft/restbed/byte.hpp"
#include "corvusoft/restbed/logger.hpp"
//External Includes
//System Namespaces
//Project Namespaces
//External Namespaces
namespace restbed
{
//Forward Declarations
class WebSocket;
namespace detail
{
//Forward Declarations
class SocketImpl;
class WebSocketManagerImpl;
class WebSocketImpl
{
public:
//Friends
//Definitions
//Constructors
WebSocketImpl( void );
virtual ~WebSocketImpl( void );
//Functionality
void log( const Logger::Level level, const std::string& message ) const;
void listen( const std::shared_ptr< WebSocket > socket );
void parse_flags( const Bytes data, const std::shared_ptr< WebSocket > socket );
void parse_payload( const Bytes data, Bytes packet, const std::shared_ptr< WebSocket > socket );
void parse_length_and_mask( const Bytes data, Bytes packet, const std::shared_ptr< WebSocket > socket );
//Getters
//Setters
//Operators
//Properties
std::string m_key = "";
bool m_error_handler_invoked = false;
std::shared_ptr< Logger > m_logger = nullptr;
std::shared_ptr< SocketImpl > m_socket = nullptr;
std::shared_ptr< WebSocketManagerImpl > m_manager = nullptr;
std::function< void ( const std::shared_ptr< WebSocket > ) > m_open_handler = nullptr;
std::function< void ( const std::shared_ptr< WebSocket > ) > m_close_handler = nullptr;
std::function< void ( const std::shared_ptr< WebSocket >, const std::error_code ) > m_error_handler = nullptr;
std::function< void ( const std::shared_ptr< WebSocket >, const std::shared_ptr< WebSocketMessage > ) > m_message_handler = nullptr;
protected:
//Friends
//Definitions
//Constructors
//Functionality
//Getters
//Setters
//Operators
//Properties
private:
//Friends
//Definitions
//Constructors
WebSocketImpl( const WebSocketImpl& original ) = delete;
//Functionality
//Getters
//Setters
//Operators
WebSocketImpl& operator =( const WebSocketImpl& value ) = delete;
//Properties
};
}
}

View File

@@ -0,0 +1,313 @@
/*
* Copyright 2013-2020, Corvusoft Ltd, All Rights Reserved.
*/
//System Includes
#include <tuple>
#include <string>
#include <random>
#include <ciso646>
#include <system_error>
//Project Includes
#include "corvusoft/restbed/logger.hpp"
#include "corvusoft/restbed/session.hpp"
#include "corvusoft/restbed/request.hpp"
#include "corvusoft/restbed/web_socket.hpp"
#include "corvusoft/restbed/web_socket_message.hpp"
#include "corvusoft/restbed/detail/socket_impl.hpp"
#include "corvusoft/restbed/detail/request_impl.hpp"
#include "corvusoft/restbed/detail/session_impl.hpp"
#include "corvusoft/restbed/detail/web_socket_impl.hpp"
#include "corvusoft/restbed/detail/web_socket_manager_impl.hpp"
//External Includes
//System Namespaces
using std::get;
using std::tuple;
using std::string;
using std::mt19937;
using std::function;
using std::to_string;
using std::shared_ptr;
using std::error_code;
using std::make_shared;
using std::random_device;
using std::placeholders::_1;
using std::uniform_int_distribution;
//Project Namespaces
using restbed::detail::WebSocketManagerImpl;
//External Namespaces
namespace restbed
{
namespace detail
{
WebSocketManagerImpl::WebSocketManagerImpl( void ) : m_logger( nullptr ),
m_sockets( )
{
return;
}
shared_ptr< WebSocketMessage > WebSocketManagerImpl::parse( const Bytes& packet )
{
if ( packet.empty( ) )
{
return nullptr;
}
Byte byte = packet[ 0 ];
auto message = make_shared< WebSocketMessage >( );
message->set_final_frame_flag( ( byte & 0x80 ) ? true : false );
message->set_reserved_flags( ( byte & 0x40 ) ? true : false,
( byte & 0x20 ) ? true : false,
( byte & 0x10 ) ? true : false );
message->set_opcode( static_cast< WebSocketMessage::OpCode >( byte & 0x0F ) );
const auto packet_length = packet.size( );
if ( packet_length == 1 )
{
return message;
}
byte = packet[ 1 ];
message->set_mask_flag( ( byte & 0x80 ) ? true : false );
message->set_length( byte & 0x7F );
if ( packet_length == 2 )
{
return message;
}
size_t offset = 2;
uint64_t length = message->get_length( );
if ( length == 126 )
{
if ( ( packet_length - ( offset + 1 ) ) <= 0 )
{
return nullptr;
}
length = packet[ offset++ ] << 8;
length |= packet[ offset++ ] ;
message->set_extended_length( length );
}
else if ( length == 127 )
{
if ( ( packet_length - ( offset + 3 ) ) <= 0 )
{
return nullptr;
}
length |= packet[ offset++ ] << 24;
length |= packet[ offset++ ] << 16;
length |= packet[ offset++ ] << 8;
length = packet[ offset++ ] ;
message->set_extended_length( length );
}
if ( message->get_mask_flag( ) == true )
{
if ( ( packet_length - ( offset + 3 ) ) <= 0 )
{
return nullptr;
}
uint32_t mask = packet[ offset++ ] << 24;
mask |= packet[ offset++ ] << 16;
mask |= packet[ offset++ ] << 8;
mask |= packet[ offset++ ] ;
message->set_mask( mask );
}
Bytes payload( packet.begin( ) + offset, packet.end( ) );
if ( message->get_mask_flag( ) == true )
{
auto masking_key = message->get_mask( );
Byte mask[ 4 ] = { };
mask[ 0 ] = ( masking_key >> 24 ) & 0xFF;
mask[ 1 ] = ( masking_key >> 16 ) & 0xFF;
mask[ 2 ] = ( masking_key >> 8 ) & 0xFF;
mask[ 3 ] = masking_key & 0xFF;
for ( size_t index = 0; index < payload.size( ); index++ )
{
payload[ index ] ^= mask[ index % 4 ];
}
}
message->set_data( payload );
return message;
}
Bytes WebSocketManagerImpl::compose( const shared_ptr< WebSocketMessage >& message )
{
Byte byte = 0x80;
if ( message->get_final_frame_flag( ) == false )
{
byte = 0x00;
}
auto reserved_flags = message->get_reserved_flags( );
if ( get< 0 >( reserved_flags ) )
{
byte |= 0x40;
}
if ( get< 1 >( reserved_flags ) )
{
byte |= 0x20;
}
if ( get< 2 >( reserved_flags ) )
{
byte |= 0x10;
}
byte |= ( message->get_opcode( ) & 0x0F );
Bytes frame = { byte };
auto length = message->get_length( );
auto mask_flag = message->get_mask_flag( );
if ( length == 126 )
{
auto extended_length = message->get_extended_length( );
frame.push_back( ( mask_flag ) ? 254 : 126 );
frame.push_back( ( extended_length >> 8 ) & 0xFF );
frame.push_back( extended_length & 0xFF );
}
else if ( length == 127 )
{
auto extended_length = message->get_extended_length( );
frame.push_back( ( mask_flag ) ? 255 : 127 );
frame.push_back( ( extended_length >> 56 ) & 0xFF );
frame.push_back( ( extended_length >> 48 ) & 0xFF );
frame.push_back( ( extended_length >> 40 ) & 0xFF );
frame.push_back( ( extended_length >> 32 ) & 0xFF );
frame.push_back( ( extended_length >> 24 ) & 0xFF );
frame.push_back( ( extended_length >> 16 ) & 0xFF );
frame.push_back( ( extended_length >> 8 ) & 0xFF );
frame.push_back( extended_length & 0xFF );
}
else
{
if ( mask_flag )
{
length |= 0x80;
}
else
{
length &= ~0x80;
}
frame.push_back( length );
}
if ( mask_flag )
{
auto masking_key = message->get_mask( );
uint8_t mask[ 4 ] = { };
mask[ 0 ] = masking_key & 0xFF;
mask[ 1 ] = ( masking_key >> 8 ) & 0xFF;
mask[ 2 ] = ( masking_key >> 16 ) & 0xFF;
mask[ 3 ] = ( masking_key >> 24 ) & 0xFF;
frame.push_back( mask[ 3 ] );
frame.push_back( mask[ 2 ] );
frame.push_back( mask[ 1 ] );
frame.push_back( mask[ 0 ] );
auto data = message->get_data( );
for ( size_t index = 0; index < data.size( ); index++ )
{
auto datum = data.at( index );
datum ^= mask[ index % 4 ];
frame.push_back( datum );
}
}
else
{
auto data = message->get_data( );
frame.insert( frame.end( ), data.begin( ), data.end( ) );
}
return frame;
}
shared_ptr< WebSocket > WebSocketManagerImpl::create( const std::shared_ptr< Session >& session )
{
if ( session == nullptr )
{
return nullptr;
}
static auto& charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
thread_local static mt19937 generator{ random_device{ }( ) };
thread_local static uniform_int_distribution< string::size_type > pick( 0, sizeof( charset ) - 2 );
string key;
auto length = 16;
key.reserve( length );
while( length-- ) key += charset[ pick( generator ) ];
auto socket = make_shared< WebSocket >( );
socket->set_key( key );
socket->set_logger( m_logger );
socket->set_socket( session->m_pimpl->m_request->m_pimpl->m_socket );
socket->m_pimpl->m_manager = shared_from_this( );
m_sockets.insert( make_pair( key, socket ) );
return socket;
}
shared_ptr< WebSocket > WebSocketManagerImpl::read( const string& key )
{
auto socket = m_sockets.find( key );
return ( socket not_eq m_sockets.end( ) ) ? socket->second : nullptr;
}
shared_ptr< WebSocket > WebSocketManagerImpl::update( const shared_ptr< WebSocket >& socket )
{
return socket;
}
void WebSocketManagerImpl::destroy( const shared_ptr< WebSocket >& socket )
{
if ( socket == nullptr )
{
return;
}
m_sockets.erase( socket->get_key( ) );
}
shared_ptr< Logger > WebSocketManagerImpl::get_logger( void ) const
{
return m_logger;
}
void WebSocketManagerImpl::set_logger( const shared_ptr< Logger >& value )
{
m_logger = value;
}
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2013-2020, Corvusoft Ltd, All Rights Reserved.
*/
#pragma once
//System Includes
#include <map>
#include <memory>
#include <functional>
//Project Includes
#include "corvusoft/restbed/byte.hpp"
//External Includes
//System Namespaces
//Project Namespaces
//External Namespaces
namespace restbed
{
//Forward Declarations
class Logger;
class Session;
class WebSocket;
class WebSocketMessage;
namespace detail
{
//Forward Declarations
class SocketImpl;
class WebSocketManagerImpl : public std::enable_shared_from_this< WebSocketManagerImpl >
{
public:
//Friends
//Definitions
//Constructors
WebSocketManagerImpl( void );
~WebSocketManagerImpl( void ) = default;
//Functionality
std::shared_ptr< WebSocketMessage > parse( const Bytes& packet );
Bytes compose( const std::shared_ptr< WebSocketMessage >& message );
std::shared_ptr< WebSocket > create( const std::shared_ptr< Session >& session );
std::shared_ptr< WebSocket > read( const std::string& key );
std::shared_ptr< WebSocket > update( const std::shared_ptr< WebSocket >& socket );
void destroy( const std::shared_ptr< WebSocket >& socket );
//Getters
std::shared_ptr< Logger > get_logger( void ) const;
//Setters
void set_logger( const std::shared_ptr< Logger >& value );
//Operators
//Properties
protected:
//Friends
//Definitions
//Constructors
//Functionality
//Getters
//Setters
//Operators
//Properties
private:
//Friends
//Definitions
//Constructors
WebSocketManagerImpl( const WebSocketManagerImpl& original ) = delete;
//Functionality
//Getters
//Setters
//Operators
WebSocketManagerImpl& operator =( const WebSocketManagerImpl& value ) = delete;
//Properties
std::shared_ptr< Logger > m_logger;
std::map< std::string, std::shared_ptr< WebSocket > > m_sockets;
};
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2013-2020, Corvusoft Ltd, All Rights Reserved.
*/
#pragma once
//System Includes
#include <cstdint>
//Project Includes
#include <corvusoft/restbed/byte.hpp>
#include <corvusoft/restbed/web_socket.hpp>
//External Includes
//System Namespaces
//Project Namespaces
//External Namespaces
namespace restbed
{
//Forward Declarations
namespace detail
{
//Forward Declarations
struct WebSocketMessageImpl
{
Bytes m_data = { };
std::uint32_t m_mask = 0;
std::uint8_t m_length = 0;
std::uint64_t m_extended_length = 0;
bool m_mask_flag = false;
bool m_final_frame_flag = true;
bool m_reserved_flag_one = false;
bool m_reserved_flag_two = false;
bool m_reserved_flag_three = false;
WebSocketMessage::OpCode m_opcode = WebSocketMessage::OpCode::BINARY_FRAME;
};
}
}