480 lines
14 KiB
C++
480 lines
14 KiB
C++
|
/*
|
||
|
* Copyright 2013-2020, Corvusoft Ltd, All Rights Reserved.
|
||
|
*/
|
||
|
|
||
|
//System Includes
|
||
|
#include <thread>
|
||
|
#include <vector>
|
||
|
#include <utility>
|
||
|
#include <cstdint>
|
||
|
#include <ciso646>
|
||
|
#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/service.hpp"
|
||
|
#include "corvusoft/restbed/session.hpp"
|
||
|
#include "corvusoft/restbed/resource.hpp"
|
||
|
#include "corvusoft/restbed/settings.hpp"
|
||
|
#include "corvusoft/restbed/ssl_settings.hpp"
|
||
|
#include "corvusoft/restbed/session_manager.hpp"
|
||
|
#include "corvusoft/restbed/detail/service_impl.hpp"
|
||
|
#include "corvusoft/restbed/detail/resource_impl.hpp"
|
||
|
#include "corvusoft/restbed/detail/web_socket_manager_impl.hpp"
|
||
|
|
||
|
//External Includes
|
||
|
#include <asio/io_service.hpp>
|
||
|
#include <asio/steady_timer.hpp>
|
||
|
|
||
|
#ifdef BUILD_SSL
|
||
|
#include <asio/ssl.hpp>
|
||
|
#endif
|
||
|
|
||
|
//System Namespaces
|
||
|
using std::map;
|
||
|
using std::bind;
|
||
|
using std::promise;
|
||
|
using std::future;
|
||
|
using std::future_status;
|
||
|
using std::async;
|
||
|
using std::launch;
|
||
|
using std::string;
|
||
|
using std::vector;
|
||
|
using std::function;
|
||
|
using std::exception;
|
||
|
using std::to_string;
|
||
|
using std::unique_ptr;
|
||
|
using std::shared_ptr;
|
||
|
using std::error_code;
|
||
|
using std::make_shared;
|
||
|
using std::stable_sort;
|
||
|
using std::runtime_error;
|
||
|
using std::chrono::seconds;
|
||
|
using std::invalid_argument;
|
||
|
using std::chrono::milliseconds;
|
||
|
using std::chrono::steady_clock;
|
||
|
using std::chrono::duration_cast;
|
||
|
|
||
|
//Project Namespaces
|
||
|
using restbed::detail::ServiceImpl;
|
||
|
using restbed::detail::WebSocketManagerImpl;
|
||
|
|
||
|
//External Namespaces
|
||
|
using asio::io_service;
|
||
|
using asio::steady_timer;
|
||
|
|
||
|
namespace restbed
|
||
|
{
|
||
|
Service::Service( void ) : m_pimpl( new ServiceImpl )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Service::~Service( void )
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
stop( );
|
||
|
}
|
||
|
catch ( ... )
|
||
|
{
|
||
|
m_pimpl->log( Logger::WARNING, "Service failed graceful wind down." );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool Service::is_up( void ) const
|
||
|
{
|
||
|
return m_pimpl->m_uptime not_eq steady_clock::time_point::min( );
|
||
|
}
|
||
|
|
||
|
bool Service::is_down( void ) const
|
||
|
{
|
||
|
return not is_up( );
|
||
|
}
|
||
|
|
||
|
void Service::stop( void )
|
||
|
{
|
||
|
m_pimpl->m_uptime = steady_clock::time_point::min( );
|
||
|
|
||
|
if ( m_pimpl->m_io_service not_eq nullptr )
|
||
|
{
|
||
|
m_pimpl->m_io_service->stop( );
|
||
|
}
|
||
|
|
||
|
if ( m_pimpl->m_session_manager not_eq nullptr )
|
||
|
{
|
||
|
m_pimpl->m_session_manager->stop( );
|
||
|
}
|
||
|
|
||
|
if ( m_pimpl->m_workers_stopped )
|
||
|
{
|
||
|
m_pimpl->m_workers_stopped->wait_for( seconds( 1 ) );
|
||
|
m_pimpl->m_workers_stopped.reset( );
|
||
|
}
|
||
|
|
||
|
if ( m_pimpl->m_logger not_eq nullptr )
|
||
|
{
|
||
|
m_pimpl->log( Logger::INFO, "Service halted." );
|
||
|
m_pimpl->m_logger->stop( );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Service::start( const shared_ptr< const Settings >& settings )
|
||
|
{
|
||
|
m_pimpl->setup_signal_handler( );
|
||
|
|
||
|
m_pimpl->m_settings = settings;
|
||
|
|
||
|
if ( m_pimpl->m_settings == nullptr )
|
||
|
{
|
||
|
m_pimpl->m_settings = make_shared< Settings >( );
|
||
|
}
|
||
|
|
||
|
#ifdef BUILD_SSL
|
||
|
m_pimpl->m_ssl_settings = m_pimpl->m_settings->get_ssl_settings( );
|
||
|
#else
|
||
|
|
||
|
if ( m_pimpl->m_settings->get_ssl_settings( ) not_eq nullptr )
|
||
|
{
|
||
|
throw runtime_error( "Not Implemented! Rebuild Restbed with SSL functionality enabled." );
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
if ( m_pimpl->m_logger not_eq nullptr )
|
||
|
{
|
||
|
m_pimpl->m_logger->start( m_pimpl->m_settings );
|
||
|
}
|
||
|
|
||
|
if ( m_pimpl->m_session_manager == nullptr )
|
||
|
{
|
||
|
m_pimpl->m_session_manager = make_shared< SessionManager >( );
|
||
|
}
|
||
|
|
||
|
m_pimpl->m_session_manager->start( m_pimpl->m_settings );
|
||
|
|
||
|
m_pimpl->m_web_socket_manager = make_shared< WebSocketManagerImpl >( );
|
||
|
|
||
|
stable_sort( m_pimpl->m_rules.begin( ), m_pimpl->m_rules.end( ), [ ]( const shared_ptr< const Rule >& lhs, const shared_ptr< const Rule >& rhs )
|
||
|
{
|
||
|
return lhs->get_priority( ) < rhs->get_priority( );
|
||
|
} );
|
||
|
|
||
|
#ifdef BUILD_IPC
|
||
|
m_pimpl->ipc_start( );
|
||
|
#endif
|
||
|
m_pimpl->http_start( );
|
||
|
#ifdef BUILD_SSL
|
||
|
m_pimpl->https_start( );
|
||
|
#endif
|
||
|
for ( const auto& route : m_pimpl->m_resource_paths )
|
||
|
{
|
||
|
auto path = String::format( "/%s/%s", m_pimpl->m_settings->get_root( ).data( ), route.second.data( ) );
|
||
|
path = String::replace( "//", "/", path );
|
||
|
|
||
|
m_pimpl->log( Logger::INFO, String::format( "Resource published on route '%s'.", path.data( ) ) );
|
||
|
}
|
||
|
|
||
|
if ( m_pimpl->m_ready_handler not_eq nullptr )
|
||
|
{
|
||
|
m_pimpl->m_io_service->post( m_pimpl->m_ready_handler );
|
||
|
}
|
||
|
|
||
|
m_pimpl->m_uptime = steady_clock::now( );
|
||
|
unsigned int limit = m_pimpl->m_settings->get_worker_limit( );
|
||
|
|
||
|
if ( limit == 0 )
|
||
|
{
|
||
|
m_pimpl->m_io_service->run( );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
promise<void> all_signalled;
|
||
|
m_pimpl->m_workers_stopped = unique_ptr<future<void> >(new future<void>(all_signalled.get_future()));
|
||
|
vector<future<void> > signals;
|
||
|
for ( unsigned int count = 0; count < limit; count++ )
|
||
|
{
|
||
|
signals.push_back( async(launch::async, [ this ]( )
|
||
|
{
|
||
|
m_pimpl->m_io_service->run( );
|
||
|
} ) );
|
||
|
}
|
||
|
|
||
|
try
|
||
|
{
|
||
|
while (!signals.empty())
|
||
|
{
|
||
|
signals.erase(std::remove_if(signals.begin(), signals.end(),
|
||
|
[](future<void>& signal) {
|
||
|
return (signal.wait_for(milliseconds(5)) == future_status::ready) ?
|
||
|
(signal.get(), true) : // this will throw if the worker thread ended with an exception
|
||
|
false;
|
||
|
}),
|
||
|
signals.end());
|
||
|
}
|
||
|
all_signalled.set_value();
|
||
|
}
|
||
|
catch(...)
|
||
|
{
|
||
|
m_pimpl->m_io_service->stop( );
|
||
|
all_signalled.set_value();
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Service::restart( const shared_ptr< const Settings >& settings )
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
stop( );
|
||
|
}
|
||
|
catch ( ... )
|
||
|
{
|
||
|
m_pimpl->log( Logger::WARNING, "Service failed graceful reboot." );
|
||
|
}
|
||
|
|
||
|
start( settings );
|
||
|
}
|
||
|
|
||
|
void Service::add_rule( const shared_ptr< Rule >& rule )
|
||
|
{
|
||
|
if ( is_up( ) )
|
||
|
{
|
||
|
throw runtime_error( "Runtime modifications of the service are prohibited." );
|
||
|
}
|
||
|
|
||
|
if ( rule not_eq nullptr )
|
||
|
{
|
||
|
m_pimpl->m_rules.push_back( rule );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Service::add_rule( const shared_ptr< Rule >& rule, const int priority )
|
||
|
{
|
||
|
if ( is_up( ) )
|
||
|
{
|
||
|
throw runtime_error( "Runtime modifications of the service are prohibited." );
|
||
|
}
|
||
|
|
||
|
if ( rule not_eq nullptr )
|
||
|
{
|
||
|
rule->set_priority( priority );
|
||
|
m_pimpl->m_rules.push_back( rule );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Service::publish( const shared_ptr< const Resource >& resource )
|
||
|
{
|
||
|
if ( is_up( ) )
|
||
|
{
|
||
|
throw runtime_error( "Runtime modifications of the service are prohibited." );
|
||
|
}
|
||
|
|
||
|
if ( resource == nullptr )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
auto paths = resource->m_pimpl->m_paths;
|
||
|
|
||
|
if ( not m_pimpl->has_unique_paths( paths ) )
|
||
|
{
|
||
|
throw invalid_argument( "Resource would pollute namespace. Please ensure all published resources have unique paths." );
|
||
|
}
|
||
|
|
||
|
for ( auto& path : paths )
|
||
|
{
|
||
|
const string sanitised_path = m_pimpl->sanitise_path( path );
|
||
|
|
||
|
m_pimpl->m_resource_paths[ sanitised_path ] = path;
|
||
|
m_pimpl->m_resource_routes[ sanitised_path ] = resource;
|
||
|
}
|
||
|
|
||
|
const auto& methods = resource->m_pimpl->m_methods;
|
||
|
m_pimpl->m_supported_methods.insert( methods.begin( ), methods.end( ) );
|
||
|
}
|
||
|
|
||
|
void Service::suppress( const shared_ptr< const Resource >& resource )
|
||
|
{
|
||
|
if ( is_up( ) )
|
||
|
{
|
||
|
throw runtime_error( "Runtime modifications of the service are prohibited." );
|
||
|
}
|
||
|
|
||
|
if ( resource == nullptr )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
for ( const auto& path : resource->m_pimpl->m_paths )
|
||
|
{
|
||
|
if ( m_pimpl->m_resource_routes.erase( path ) )
|
||
|
{
|
||
|
m_pimpl->log( Logger::INFO, String::format( "Suppressed resource route '%s'.", path.data( ) ) );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_pimpl->log( Logger::WARNING, String::format( "Failed to suppress resource route '%s'; Not Found!", path.data( ) ) );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Service::schedule( const function< void ( void ) >& task, const milliseconds& interval )
|
||
|
{
|
||
|
if ( task == nullptr )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( interval == milliseconds::zero( ) )
|
||
|
{
|
||
|
m_pimpl->m_io_service->post( task );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
auto timer = make_shared< steady_timer >( *m_pimpl->m_io_service );
|
||
|
timer->expires_from_now( interval );
|
||
|
timer->async_wait( [ this, task, interval, timer ]( const error_code& )
|
||
|
{
|
||
|
task( );
|
||
|
schedule( task, interval );
|
||
|
} );
|
||
|
}
|
||
|
|
||
|
const seconds Service::get_uptime( void ) const
|
||
|
{
|
||
|
if ( is_down( ) )
|
||
|
{
|
||
|
return seconds( 0 );
|
||
|
}
|
||
|
|
||
|
return duration_cast< seconds >( steady_clock::now( ) - m_pimpl->m_uptime );
|
||
|
}
|
||
|
|
||
|
const shared_ptr< const Uri > Service::get_http_uri( void ) const
|
||
|
{
|
||
|
return m_pimpl->get_http_uri( );
|
||
|
}
|
||
|
|
||
|
const shared_ptr< const Uri > Service::get_https_uri( void ) const
|
||
|
{
|
||
|
return m_pimpl->get_https_uri( );
|
||
|
}
|
||
|
|
||
|
void Service::set_logger( const shared_ptr< Logger >& value )
|
||
|
{
|
||
|
if ( is_up( ) )
|
||
|
{
|
||
|
throw runtime_error( "Runtime modifications of the service are prohibited." );
|
||
|
}
|
||
|
|
||
|
m_pimpl->m_logger = value;
|
||
|
}
|
||
|
|
||
|
void Service::set_session_manager( const shared_ptr< SessionManager >& value )
|
||
|
{
|
||
|
if ( is_up( ) )
|
||
|
{
|
||
|
throw runtime_error( "Runtime modifications of the service are prohibited." );
|
||
|
}
|
||
|
|
||
|
m_pimpl->m_session_manager = value;
|
||
|
}
|
||
|
|
||
|
void Service::set_ready_handler( const function< void ( Service& ) >& value )
|
||
|
{
|
||
|
if ( is_up( ) )
|
||
|
{
|
||
|
throw runtime_error( "Runtime modifications of the service are prohibited." );
|
||
|
}
|
||
|
|
||
|
if ( value not_eq nullptr )
|
||
|
{
|
||
|
m_pimpl->m_ready_handler = bind( value, std::ref( *this ) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Service::set_signal_handler( const int signal, const function< void ( const int ) >& value )
|
||
|
{
|
||
|
if ( is_up( ) )
|
||
|
{
|
||
|
throw runtime_error( "Runtime modifications of the service are prohibited." );
|
||
|
}
|
||
|
|
||
|
if ( value not_eq nullptr )
|
||
|
{
|
||
|
m_pimpl->m_signal_handlers[ signal ] = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Service::set_not_found_handler( const function< void ( const shared_ptr< Session > ) >& value )
|
||
|
{
|
||
|
if ( is_up( ) )
|
||
|
{
|
||
|
throw runtime_error( "Runtime modifications of the service are prohibited." );
|
||
|
}
|
||
|
|
||
|
m_pimpl->m_not_found_handler = value;
|
||
|
}
|
||
|
|
||
|
void Service::set_method_not_allowed_handler( const function< void ( const shared_ptr< Session > ) >& value )
|
||
|
{
|
||
|
if ( is_up( ) )
|
||
|
{
|
||
|
throw runtime_error( "Runtime modifications of the service are prohibited." );
|
||
|
}
|
||
|
|
||
|
m_pimpl->m_method_not_allowed_handler = value;
|
||
|
}
|
||
|
|
||
|
void Service::set_method_not_implemented_handler( const function< void ( const shared_ptr< Session > ) >& value )
|
||
|
{
|
||
|
if ( is_up( ) )
|
||
|
{
|
||
|
throw runtime_error( "Runtime modifications of the service are prohibited." );
|
||
|
}
|
||
|
|
||
|
m_pimpl->m_method_not_implemented_handler = value;
|
||
|
}
|
||
|
|
||
|
void Service::set_failed_filter_validation_handler( const function< void ( const shared_ptr< Session > ) >& value )
|
||
|
{
|
||
|
if ( is_up( ) )
|
||
|
{
|
||
|
throw runtime_error( "Runtime modifications of the service are prohibited." );
|
||
|
}
|
||
|
|
||
|
m_pimpl->m_failed_filter_validation_handler = value;
|
||
|
}
|
||
|
|
||
|
void Service::set_error_handler( const function< void ( const int, const exception&, const shared_ptr< Session > ) >& value )
|
||
|
{
|
||
|
if ( is_up( ) )
|
||
|
{
|
||
|
throw runtime_error( "Runtime modifications of the service are prohibited." );
|
||
|
}
|
||
|
|
||
|
if ( value == nullptr )
|
||
|
{
|
||
|
m_pimpl->m_error_handler = ServiceImpl::default_error_handler;
|
||
|
}
|
||
|
|
||
|
m_pimpl->m_error_handler = value;
|
||
|
}
|
||
|
|
||
|
void Service::set_authentication_handler( const function< void ( const shared_ptr< Session >, const function< void ( const shared_ptr< Session > ) >& ) >& value )
|
||
|
{
|
||
|
if ( is_up( ) )
|
||
|
{
|
||
|
throw runtime_error( "Runtime modifications of the service are prohibited." );
|
||
|
}
|
||
|
|
||
|
m_pimpl->m_authentication_handler = value;
|
||
|
}
|
||
|
}
|