#include <iostream>
namespace sample
{
struct book_t
{
book_t() = default;
book_t(
std::string author,
std::string title )
: m_author{ std::move( author ) }
, m_title{ std::move( title ) }
{}
std::string m_author;
std::string m_title;
};
[[nodiscard]]
book_t
deserialize( std::string_view body )
{
constexpr std::string_view author_tag{ "author:" };
constexpr std::string_view title_tag{ "title:" };
constexpr std::string_view separator{ ";;;" };
book_t result;
if( author_tag != body.substr( 0u, author_tag.size() ) )
throw std::runtime_error{ "Unable to parse (no 'author:' tag)" };
else
body = body.substr( author_tag.size() );
if( auto n = body.find( separator ); n == std::string_view::npos )
throw std::runtime_error{ "Unable to parse (no value separator #1)" };
else if( 0u == n )
throw std::runtime_error{ "Unable to parse (no author name)" };
else
{
result.m_author = body.substr( 0u, n );
body = body.substr( n + separator.size() );
}
if( title_tag != body.substr( 0u, title_tag.size() ) )
throw std::runtime_error{ "Unable to parse (no 'title:' tag)" };
else
body = body.substr( title_tag.size() );
if( body.empty() )
throw std::runtime_error{ "Unable to parse (no title)" };
if( auto n = body.find( separator ); n == std::string_view::npos )
result.m_title = body;
else if( 0u == n )
throw std::runtime_error{ "Unable to parse (no title)" };
else
{
result.m_title = body.substr( 0u, n );
body = body.substr( n + separator.size() );
if( !body.empty() )
{
throw std::runtime_error{ "Unable to parse (additional data found)" };
}
}
return result;
}
using book_collection_t = std::vector< book_t >;
using router_t = rr::express_router_t<>;
class books_handler_t
{
public :
explicit books_handler_t( book_collection_t & books )
: m_books( books )
{}
books_handler_t( const books_handler_t & ) = delete;
books_handler_t( books_handler_t && ) = delete;
auto on_books_list(
{
auto resp = init_resp( req->create_response() );
resp.set_body(
"Book collection (book count: " +
std::to_string( m_books.size() ) + ")\n" );
for( std::size_t i = 0; i < m_books.size(); ++i )
{
resp.append_body( std::to_string( i + 1 ) + ". " );
const auto & b = m_books[ i ];
resp.append_body( b.m_title + "[" + b.m_author + "]\n" );
}
return resp.done();
}
auto on_book_get(
{
auto resp = init_resp( req->create_response() );
if( booknum > 0 && booknum <= m_books.size() )
{
const auto & b = m_books[ booknum - 1 ];
resp.set_body(
"Book #" + std::to_string( booknum ) + " is: " +
b.m_title + " [" + b.m_author + "]\n" );
}
else
{
resp.set_body(
"No book with #" + std::to_string( booknum ) + "\n" );
}
return resp.done();
}
auto on_author_get(
{
auto resp = init_resp( req->create_response() );
try
{
resp.set_body( "Books of " + author + ":\n" );
for( std::size_t i = 0; i < m_books.size(); ++i )
{
const auto & b = m_books[ i ];
if( author == b.m_author )
{
resp.append_body( std::to_string( i + 1 ) + ". " );
resp.append_body( b.m_title + "[" + b.m_author + "]\n" );
}
}
}
catch( const std::exception & )
{
mark_as_bad_request( resp );
}
return resp.done();
}
auto on_new_book(
{
auto resp = init_resp( req->create_response() );
try
{
m_books.emplace_back( deserialize( req->body() ) );
}
catch( const std::exception & )
{
mark_as_bad_request( resp );
}
return resp.done();
}
auto on_book_update(
{
auto resp = init_resp( req->create_response() );
try
{
auto b = deserialize( req->body() );
if( booknum > 0u && booknum <= m_books.size() )
{
m_books[ booknum - 1 ] = b;
}
else
{
mark_as_bad_request( resp );
resp.set_body( "No book with #" + std::to_string( booknum ) + "\n" );
}
}
catch( const std::exception & )
{
mark_as_bad_request( resp );
}
return resp.done();
}
auto on_book_delete(
{
auto resp = init_resp( req->create_response() );
if( booknum > 0u && booknum <= m_books.size() )
{
const auto & b = m_books[ booknum - 1 ];
resp.set_body(
"Delete book #" + std::to_string( booknum ) + ": " +
b.m_title + "[" + b.m_author + "]\n" );
m_books.erase( m_books.begin() + ( booknum - 1 ) );
}
else
{
resp.set_body(
"No book with #" + std::to_string( booknum ) + "\n" );
}
return resp.done();
}
private :
book_collection_t & m_books;
template < typename RESP >
static RESP
init_resp( RESP resp )
{
resp
.append_header( "Server", "RESTinio sample server /v.0.6" )
.append_header_date_field()
.append_header( "Content-Type", "text/plain; charset=utf-8" );
return resp;
}
template < typename RESP >
static void
mark_as_bad_request( RESP & resp )
{
}
};
auto server_handler( book_collection_t & book_collection )
{
auto router = std::make_unique< router_t >();
auto handler = std::make_shared< books_handler_t >( std::ref(book_collection) );
auto by = [&]( auto method ) {
using namespace std::placeholders;
return std::bind( method, handler, _1, _2 );
};
auto method_not_allowed = []( const auto & req, auto ) {
.connection_close()
.done();
};
router->http_get( "/", by( &books_handler_t::on_books_list ) );
router->http_post( "/", by( &books_handler_t::on_new_book ) );
router->add_handler(
restinio::http_method_get(), restinio::http_method_post() ),
"/", method_not_allowed );
router->http_get( "/author/:author", by( &books_handler_t::on_author_get ) );
router->add_handler(
"/author/:author", method_not_allowed );
router->http_get(
R"(/:booknum(\d+))",
by( &books_handler_t::on_book_get ) );
router->http_put(
R"(/:booknum(\d+))",
by( &books_handler_t::on_book_update ) );
router->http_delete(
R"(/:booknum(\d+))",
by( &books_handler_t::on_book_delete ) );
router->add_handler(
restinio::http_method_get(),
restinio::http_method_post(),
restinio::http_method_delete() ),
R"(/:booknum(\d+))", method_not_allowed );
return router;
}
}
int main()
{
using namespace sample;
using namespace std::chrono;
try
{
using traits_t =
router_t >;
book_collection_t book_collection{
{ "Agatha Christie", "Murder on the Orient Express" },
{ "Agatha Christie", "Sleeping Murder" },
{ "B. Stroustrup", "The C++ Programming Language" }
};
.address( "localhost" )
.request_handler( server_handler( book_collection ) )
.read_next_http_message_timelimit( 10s )
.write_http_response_timelimit( 1s )
.handle_request_timeout( 1s ) );
}
catch( const std::exception & ex )
{
std::cerr << "Error: " << ex.what() << std::endl;
return 1;
}
return 0;
}
Timer factory implementation using asio timers.
Include all core header files in one.
impl::fixed_size_none_of_matcher_t< sizeof...(Args) > none_of_methods(Args &&...args)
A factory function that creates a method_matcher that allows a method if it isn't found in the list o...
std::string unescape_percent_encoding(const string_view_t data)
std::shared_ptr< request_t > request_handle_t
An alias for handle for incoming request without additional extra-data.
run_on_this_thread_settings_t< Traits > on_this_thread()
A special marker for the case when http_server must be run on the context of the current thread.
ostream_logger_t< null_mutex_t > single_threaded_ostream_logger_t
void run(asio_ns::io_context &ioctx, run_on_this_thread_settings_t< Traits > &&settings)
Helper function for running http server until ctrl+c is hit.
Value_Type cast_to(string_view_t str_representation)
Cast string representation to a given type.
http_status_line_t status_method_not_allowed()
http_status_line_t status_bad_request()