Example
The following shows sample use of the router. See basic test and other unit tests for more samples.
#include <router/router.h>
using std::operator ""s;
using std::operator ""sv;
int main()
{
struct Request
{
// pass whatever you need as user data
} request;
const auto method = "GET"sv;
spt::http::router::HttpRouter<const Request&, bool> r;
r.add( "POST"sv, "/device/sensor/"sv, []( const Request&, spt::http::router::HttpRouter<const Request&, bool>::MapType args )
{
assert( args.empty() );
return true;
} );
r.add( method, "/device/sensor/"sv, []( const Request&, auto args )
{
assert( args.empty() );
return true;
} );
r.add( "PUT"sv, "/device/sensor/id/{id}"sv, []( const Request&, auto args )
{
assert( args.size() == 1 );
assert( args.contains( "id"sv ) );
return true;
} );
r.add( method, "/device/sensor/id/{id}"sv, []( const Request&, auto args )
{
assert( args.size() == 1 );
assert( args.contains( "id"sv ) );
return true;
} );
r.add( method, "/device/sensor/identifier/{identifier}"sv, []( const Request&, auto args )
{
assert( args.size() == 1 );
assert( args.contains( "identifier"sv ) );
return true;
} );
r.add( method, "/device/sensor/customer/code/{code}"sv, []( const Request&, auto args )
{
assert( args.size() == 1 );
assert( args.contains( "code"sv ) );
return true;
} );
r.add( method, "/device/sensor/facility/id/{id}"sv, []( const Request&, auto args )
{
assert( args.size() == 1 );
assert( args.contains( "id"sv ) );
return true;
} );
r.add( method, "/device/sensor/count/references/{id}"sv, []( const Request&, auto args )
{
assert( args.size() == 1 );
assert( args.contains( "id"sv ) );
return true;
} );
r.add( method, "/device/sensor/history/summary/{id}"sv, []( const Request&, auto args )
{
assert( args.size() == 1 );
assert( args.contains( "id"sv ) );
return true;
} );
r.add( method, "/device/sensor/history/document/{id}"sv, []( const Request&, auto args )
{
assert( args.size() == 1 );
assert( args.contains( "id"sv ) );
return true;
} );
r.add( method, "/device/sensor/{property}/between/{start}/{end}"sv, []( const Request&, auto args )
{
assert( args.size() == 3 );
assert( args.contains( "property"sv ) );
assert( args.contains( "start"sv ) );
assert( args.contains( "end"sv ) );
return true;
} );
std::vector<std::string> urls =
{
"/device/sensor/"s,
"/device/sensor/id/6230f3069e7c9be9ff4b78a1"s, // id=6230f3069e7c9be9ff4b78a1
"/device/sensor/identifier/Integration Test Identifier"s, // identifier=Integration Test Identifier
"/device/sensor/customer/code/int-test"s, // code=int-test
"/device/sensor/history/summary/6230f3069e7c9be9ff4b78a1"s, // id=6230f3069e7c9be9ff4b78a1
"/device/sensor/history/document/6230f3069e7c9be9ff4b78a1"s, // id=6230f3069e7c9be9ff4b78a1
"/device/sensor/count/references/6230f3069e7c9be9ff4b78a1"s, // id=6230f3069e7c9be9ff4b78a1
"/device/sensor/created/between/2022-03-14T20:11:50.620Z/2022-03-16T20:11:50.620Z"s, // property=created, start=2022-03-14T20:11:50.620Z, end=2022-03-16T20:11:50.620Z
};
for ( auto&& url : urls )
{
auto resp = r.route( "GET"sv, url, request );
assert( resp );
assert( *resp );
}
auto resp = r.route( "PUT"sv, "/device/sensor/"sv );
assert( resp );
assert( !*resp ); // PUT not configured
resp = r.route( "POST"sv, "/device/sensor/history/document/{id}"sv );
assert( resp );
assert( !*resp ); // POST not configured
try
{
r.add( "PUT"sv, "/device/sensor/id/{id}"sv, []( const Request&, auto args ) { return true; } );
}
catch ( const spt::http::router::DuplicateRouteError& e )
{
// Will be caught as we registered the same route earlier
std::cerr << e.what() << '\n';
}
}
Use with nghttp2
The route
method returns a std::optional<Response>
. If no configured path matches, returns std::nullopt
(or the response from the not found handler if specified at construction time). Otherwise, returns the response from the callback function.
#include <nghttp2/asio_http2_server.h>
#include <log/NanoLog.h>
#include <router/router.h>
int main()
{
struct Request
{
explicit Request( const nghttp2::asio_http2::server::request& req ) :
header{ req.header() }, method{ req.method() },
path{ req.uri().path }, query{ req.uri().raw_query } {}
nghttp2::asio_http2::header_map header;
std::string method;
std::string path;
std::string query;
std::shared_ptr<std::string> body{ nullptr };
};
struct Response
{
nghttp2::asio_http2::header_map headers;
std::string body{ "{}" };
uint16_t status{ 200 };
bool compressed{ false };
};
auto const error404 = []( const Request&, spt::http::router::HttpRouter<const Request&, Response>::MapType ) -> Response
{
auto json = R"({"code": 404, "cause": "Not Found"})"s;
auto headers = nghttp2::asio_http2::header_map{
{ "Access-Control-Allow-Origin", { "*", false} },
{ "Access-Control-Allow-Methods", { "DELETE,GET,OPTIONS,POST,PUT", false } },
{ "Access-Control-Allow-Headers", { "*, authorization", false } },
{ "content-type", { "application/json; charset=utf-8", false } },
{ "content-length", { std::to_string( json.size() ), false } }
};
return { std::move( headers ), std::move( json ), 404, false }
}
auto const error405 - []( const Request&, spt::http::router::HttpRouter<const Request&, Response>::MapType ) -> Response
{
auto json = R"({"code": 405, "cause": "Method Not Allowed"})"s;
auto headers = nghttp2::asio_http2::header_map{
{ "Access-Control-Allow-Origin", { "*", false} },
{ "Access-Control-Allow-Methods", { "DELETE,GET,OPTIONS,POST,PUT", false } },
{ "Access-Control-Allow-Headers", { "*, authorization", false } },
{ "content-type", { "application/json; charset=utf-8", false } },
{ "content-length", { std::to_string( json.size() ), false } }
};
return { std::move( headers ), std::move( json ), 405, false }
}
auto router = spt::http::router::HttpRouter<const Request&, Response>::Builder{}.
withNotFound( error404 ).withMethodNotAllowed( error405 ).build();
// set up router as in above sample
nghttp2::asio_http2::server::http2 server;
server.num_threads( 8 );
server.handle( "/", [&router](const nghttp2::asio_http2::server::request& req,
const nghttp2::asio_http2::server::response& res)
{
auto request = Request{ req };
auto response = router.route( request.method, request.path, request );
assert( response );
res.write_head( response->status, response->headers );
res.end( std::move( response->body ) );
});
boost::system::error_code ec;
if ( server.listen_and_serve( ec, "0.0.0.0", port, true ) )
{
LOG_CRIT << "error: " << ec.message();
return 1;
}
}