Various helpers
Since v.0.6.1, RESTinio provides a set of various helpers for simplification of incoming request handling. Please note that those helpers are defined in header files not included in restinio/core.hpp
, and because of that, those header files should be included separately. For example:
#include <restinio/core.hpp>
#include <restinio/helpers/multipart_body.hpp>
try_parse_field helper function
Since v.0.6.1 the support for several standard HTTP-fields has been added to
RESTinio. This support looks like a structure from
restinio::http_field_parsers
namespace with static try_parse
method.
And the usage of such structs looks like:
#include <restinio/core.hpp>
#include <restinio/helpers/http_fields_parsers/accept.hpp>
...
auto on_request(const restinio::request_handle_t & req) {
// Try to get and parse the value of `Accept` header.
const auto opt_field = req.header().opt_value_of(
restinio::http_field::accept);
if(opt_field) { // Field is present.
using namespace restinio::http_field_parsers;
const auto parse_result = accept_value_t::try_parse(*opt_field);
if(parse_result) { // Value of the field is successfully parsed.
const accept_value_t & value = *parse_result;
... // Some usage of parsed value.
}
}
}
Since v.0.6.8 this code can be somewhat simplified by using try_parse_field
template function:
#include <restinio/core.hpp>
#include <restinio/helpers/http_fields_parsers/try_parse_field.hpp>
#include <restinio/helpers/http_fields_parsers/accept.hpp>
...
auto on_request(const restinio::request_handle_t & req) {
using namespace restinio::http_field_parsers;
// Try to get and parse the value of `Accept` header.
const auto parse_result = try_parse_field<accept_value_t>(
req, restinio::http_field::accept);
if(const auto * value =
restinio::get_if<accept_value_t>(&parse_result)) {
// Value of the field is successfully parsed.
... // Some usage of parsed value.
}
}
The try_parse_field
also allows to provide the default value that
will be used if HTTP-field is absent in the request:
#include <restinio/core.hpp>
#include <restinio/helpers/http_fields_parsers/try_parse_field.hpp>
#include <restinio/helpers/http_fields_parsers/accept.hpp>
...
auto on_request(const restinio::request_handle_t & req) {
using namespace restinio::http_field_parsers;
// Try to get and parse the value of `Accept` header.
const auto parse_result = try_parse_field<accept_value_t>(
req, restinio::http_field::accept,
// This value will be used if `Accept` is not
// present in the request.
"application/json");
if(const auto * value =
restinio::get_if<accept_value_t>(&parse_result)) {
// Value of the field is successfully parsed.
... // Some usage of parsed value.
}
}
The try_parse_field<F>
returns a variant type:
variant_t<
// For the case if HTTP-field is present and successfully parsed.
F,
// For the case when HTTP-field is not found in the request.
restinio::http_field_parsers::field_not_found_t,
// For the case when HTTP-field's value can't be parsed.
restinio::easy_parser::parse_error_t
>;
So the returned value can be handled the usual way for C++ std::variant
.
One approach, that uses get_if
function, has been shown above. Another way
is to use visit
function:
#include <restinio/core.hpp>
#include <restinio/helpers/http_fields_parsers/try_parse_field.hpp>
#include <restinio/helpers/http_fields_parsers/accept.hpp>
...
auto on_request(const restinio::request_handle_t & req) {
using namespace restinio::http_field_parsers;
// A handler of the result of parsing `Accept` header.
struct accept_handler {
... // Some app-specific internals.
void operator()(const accept_value_t & value) const {
... // Some usage of parsed value.
}
void operator()(field_not_found_t) const {
... // Handle a case when `Accept` is absent.
}
void operator()(restinio::easy_parser::parse_error_t err) const {
... // Handle parsing result.
}
};
// Try to get and parse the value of `Accept` header.
restinio::visit(
accept_handler{...},
try_parse_field<accept_value_t>(
req, restinio::http_field::accept));
...
}
Multipart body helpers
A set of helper functions for working with multipart body was added in v.0.6.1. Those functions are defined in restinio/helpers/multipart_body.hpp
header file.
enumerate_parts function
The main helper function in multipart_body.hpp
is enumerate_parts()
from restinio::multipart_body
namespace. That function accepts a reference to restinio::request_t
and a lambda-function (or function), then calls specified lambda-function for every part of multi-part body. For example:
auto on_post(const restinio::request_handle_t & req) {
using namespace restinio::multipart_body;
const auto result = enumerate_parts( *req,
[](parsed_part_t part) {
... // Some actions with the current part.
return handling_result_t::continue_enumeration;
},
"multipart", "form-data" );
if(result) {
... // Producing positive response.
}
else {
... // Producing negative response.
}
return restinio::request_accepted();
}
The enumerate_parts
function:
- finds Content-Type field for the specified request;
- parses Content-Type field, checks the media-type and extracts the value of ‘boundary’ parameter. The extracted ‘boundary’ parameter is checked for validity;
- splits the body of the specified request using value of ‘boundary’ parameter;
- enumerates every part of body, parses every part and calls specified handler for every parsed part.
The enumerate_parts
function returns expected_t<std::size_t, enumeration_error_t>
value. If the enumeration was successful (e.g. without any errors) then the returned value will hold the quantity of body’s parts passed to the specified handler. In the case of error a value from enumeration_error_t
enum will be returned. The enum enumeration_error_t
is defined in restinio::multipart_body
namespace.
Every parsed part of specified request’s body is represented by restinio::multipart_body::parsed_part_t
struct:
struct parsed_part_t
{
// HTTP-fields local for that part.
/*
* It can be empty if no HTTP-fields are found for that part.
*/
http_header_fields_t fields;
// The body of that part.
string_view_t body;
};
A temporary object of type parsed_part_t
is passed to the specified handler by rvalue reference. So the specified handler can have one on the following formats:
handling_result_t(parsed_part_t part);
handling_result_t(parsed_part_t && part);
handling_result_t(const parsed_part_t & part);
The specified handler should return a value from restinio::multipart_body::handling_result_t
enum:
handling_result_t::continue_enumeration
if the enumeration should be continued and the handler is ready for processing the next part (if that part exists);handling_result_t::stop_enumeration
if the enumeration should be stopped and there is no need to process the next part (if that part exists). In that caseenumerate_parts
finishes its work and returns successful result;handling_result_t::terminate_enumeration
if the enumeration should be aborted. In that caseenumerate_parts
finishes its work and returns negative result with codeenumeration_error_t::terminated_by_handler
.
Building blocks of enumerate_parts function
Sometimes enumerate_parts
can be too high-level function that hides a lot of details from a user. In that case low-level building blocks of enumerate_parts
can be used directly. Those helper functions are also defined in restinio::multipart_body
namespace and are available after inclusion of restinio/helpers/multipart_body.hpp
header file.
detect_boundary_for_multipart_body
The detect_boundary_for_multipart_body
function tries to acquire value of ‘boundary’ parameter from Content-Type field of the specified requests. If that parameter is found and has a valid value then this value is transformed by adding two leading hypens. This function has the following prototype:
[[nodiscard]] expected_t<std::string, enumeration_error_t>
detect_boundary_for_multipart_body(
const request_t & req,
string_view_t expected_media_type,
optional_t<string_view_t> expected_media_subtype);
Please note that parameter ‘boundary’ is handled only if Content-Type field is found for the specified request and has the expected ‘type’ value of media-type from Content-Type. The ‘subtype’ value is checked only if expected_media_subtype
is not empty.
Note also that a special value *
of expected_media_type
and expected_media_subtype
is not supported. It means that if a user specifies:
auto result = detect_boundary_for_multipart_body(req, "text", "*");
then detect_boundary_for_multipart_body
will find a boundary only if Content-Type field of the request will have text/*
media-type value.
split_multipart_body
The split_multipart_body
function splits the request’s body into parts and returns a vector of string_views for those parts. It has the following prototype:
[[nodiscard]] std::vector<string_view_t>
split_multipart_body(string_view_t body, string_view_t boundary);
Please note that the value of boundary
should be acquired from Content-Type field by a user. That value should also contain two leading hypens.
using namespace restinio::multipart_body;
const auto boundary = detect_boundary_for_multipart_body(
req, "multipart", "form-data" );
if( boundary )
{
const auto parts = split_multipart_body( req.body(), *boundary );
for( restinio::string_view_t one_part : parts )
{
... // Handling of a part.
}
}
try_parse_part
Helper function try_parse_part
tries to parse one part of a multipart body and returns parsed_part_t
object in the case of success. It has the following prototype:
[[nodiscard]]
expected_t<parsed_part_t, restinio::easy_parser::parse_error_t>
try_parse_part(string_view_t part);
A simple usage example:
using namespace restinio::multipart_body;
const auto boundary = detect_boundary_for_multipart_body(
req, "multipart", "form-data" );
if( boundary )
{
const auto parts = split_multipart_body( req.body(), *boundary );
for( restinio::string_view_t one_part : parts )
{
const auto parsed_part = try_parse_part( one_part );
if( parsed_part )
{
... // Handle the content of the parsed part.
}
}
}
File upload helpers
A set of helper functions for simplification of implementation of file upload functionality was added in v.0.6.1. Those functions are defined in restinio/helpers/file_upload.hpp
header file.
analyze_part
The helper function analyze_part
from restinio::file_upload
namespace tries to analyze the specified part and find artifacts required by RFC1867 “Form-based File Upload in HTML”. If the specified part is looking as a part with uploaded file inside then analyze_part
returns an instance of part_description_t
type. Otherwise analyze_part
return an error code. This function has the following prototype:
[[nodiscar]] expected_t<part_description_t, enumeration_error_t>
analyze_part(restinio::multipart_body::parsed_part_t parsed_part);
The analyze_part
function tries to find Content-Disposition field for the specified part. If that field is present then analyze_part
tries to find filename
and filename*
parameters in that field. If at least one of those parameters is found then analyze_part
returns part_description_t
instance where part_description_t
is:
struct part_description_t
{
// HTTP-fields local for that part.
/*
* It can be empty if no HTTP-fields are found for that part.
*/
http_header_fields_t fields;
// The body of that part.
string_view_t body;
// The value of Content-Disposition's 'name' parameter.
std::string name;
// The value of Content-Disposition's 'filename*' parameter.
/*
* This field has the value only of 'filename*' parameter was
* found in Content-Disposition field.
*
* If that field is presend then it is the original value extracted
* from Content-Disposition without any transformation. It means
* that this field will hold values defined in RFC5987 like:
* `utf-8'en-US'A%20some%20filename.txt`
*/
optional_t< std::string > filename_star;
// The value of Content-Disposition's 'filename' parameter.
/*
* This field has the value only of 'filename' parameter was
* found in Content-Disposition field.
*/
optional_t< std::string > filename;
};
An usage example:
auto on_post(const restinio::request_handle_t & req) {
using namespace restinio::multipart_body;
using namespace restinio::file_upload;
const auto result = enumerate_parts( *req,
[](parsed_part_t part) {
// Try to find an uploaded file in that part.
const auto uploaded_file = analyze_part(part);
if(uploaded_file) {
... // Some handling of the file content.
}
return handling_result_t::continue_enumeration;
},
"multipart", "form-data" );
if(result) {
... // Producing positive response.
}
else {
... // Producing negative response.
}
return restinio::request_accepted();
}
Please note that analyze_part
is a rather low-level function and it is intended to be used in complex scenarios. For simple scenarios usage of enumerate_parts_with_files
function described below is prefereble.
enumerate_parts_with_files
The helper function enumerate_parts_with_files
from restinio::file_upload
namespace tries to parse the body of the specified request into separate parts, analyses every part and calls the specified handler for every part that holds an uploaded file. This function has the following prototype:
template<typename Handler>
expected_t<std::size_t, enumeration_error_t>
enumerate_parts_with_files(
const request_t & req,
Handler && handler,
string_view_t expected_media_type = string_view_t{"multipart"},
string_view_t expected_media_subtype = string_view_t{"form-data"});
A usage example can look like:
auto on_post(const restinio::request_handle_t & req) {
using namespace restinio::file_upload;
const auto result = enumerate_parts_with_files( *req,
[](part_description_t part) {
... // Some actions with the current part.
return handling_result_t::continue_enumeration;
});
if(result) {
... // Producing positive response.
}
else {
... // Producing negative response.
}
return restinio::request_accepted();
}
The enumerate_parts_with_files
function:
- finds Content-Type field for the specified request;
- parses Content-Type field, checks the media-type and extracts the value of ‘boundary’ parameter. The extracted ‘boundary’ parameter is checked for validity;
- splits the body of the specified request using value of ‘boundary’ parameter;
- enumerates every part of body, parses every part;
- checks the presence of Content-Disposition field with ‘name’ and ‘filename*’/’filename’ parameters;
- if Content-Disposition field with required parameters is found then the specified handler is called for the current part.
The enumerate_parts_with_files
function returns expected_t<std::size_t, enumeration_error_t>
values. If the enumeration was successful (e.g. without any errors) then the returned value will hold the quantity of body’s parts passed to the specified handler. In the case of error a value from enumeration_error_t
enum will be returned. The enum enumeration_error_t
is defined in restinio::file_upload
namespace.
Every parsed part of specified request’s body is represented by restinio::file_upload::part_description_t
shown above.
A temporary object of type part_description_t
is passed to the specified handler by rvalue reference. So the specified handler can have one on the following formats:
handling_result_t(part_description_t part);
handling_result_t(part_description_t && part);
handling_result_t(const part_description_t & part);
The specified handler should return a value from restinio::file_upload::handling_result_t
enum:
handling_result_t::continue_enumeration
if the enumeration should be continued and the handler is ready for processing the next part (if that part exists);handling_result_t::stop_enumeration
if the enumeration should be stopped and there is no need to process the next part (if that part exists). In that caseenumerate_parts_with_files
finishes its work and returns successful result;handling_result_t::terminate_enumeration
if the enumeration should be aborted. In that caseenumerate_parts_with_files
finishes its work and returns negative result with codeenumeration_error_t::terminated_by_handler
.
In the case of the success enumerate_parts_with_files
returns the quantity of parts passed to the specified handler.
Authorization helpers
Since v.0.6.7 RESTinio provides some basic tools for dealing with authentification/authorization of users. Those tools are very simple and covers only trivial scenarios, so if you need something more sophisticated please open an issue.
Authorization HTTP-field parser
A parser for HTTP-fields like Authorization
and Proxy-Authorization
is available via inclusion of restinio/helpers/http_field_parser/authorization.hpp
header file. Please note that this file should be included separatelly:
#include <restinio/core.hpp>
#include <restinio/helpers/http_field_parser/authorization.hpp>
There is a struct authorization_value_t
from
restinio::http_field_parsers
namespace that holds a value from successfully
parsed HTTP-field. This struct has a static try_parse
method that performs
the parsing of HTTP-field:
auto on_request(const restinio::request_handle_t & req) {
auto opt_field_value = req->header().opt_value_of(
restinio::http_field::authorization);
if(opt_field_value) { // Authorization HTTP-field is present.
using namespace restinio::http_field_parsers;
auto auth_params = authorization_value_t::try_parse(*opt_field_value);
if(auth_params) { // HTTP-field parser successfully.
if("basic" == auth_params->auth_scheme) {
...
}
else {
...
}
}
}
...
}
A support for extraction of parameters for Basic authentification
There are two forms of try_extract_params
helper function from
restinio::http_field_parsers::basic_auth
that allows to extract parameters
for Basic authentification scheme from
a HTTP-field:
#include <restinio/core.hpp>
#include <restinio/http_field_parser/basic_auth.hpp>
...
auto on_request(const restinio::request_handle_t & req) {
using namespace restinio::http_field_parsers::basic_auth;
const auto auth_params = try_extract_params(*req,
restinio::http_field::authorization);
if(auth_params) { // Parameters successfully extracted.
if(is_valid_user(auth_params->username, auth_params->password)) {
...
}
}
...
}
If it is necessary to extract authentification/authorization parameters from a
HTTP-field with a custom name then another overload of try_extract_params
can be used:
#include <restinio/core.hpp>
#include <restinio/http_field_parser/basic_auth.hpp>
...
auto on_request(const restinio::request_handle_t & req) {
using namespace restinio::http_field_parsers::basic_auth;
const auto auth_params = try_extract_params(*req,
"X-My-Custom-Authorization");
if(auth_params) { // Parameters successfully extracted.
if(is_valid_user(auth_params->username, auth_params->password)) {
...
}
}
...
}
A support for extraction of parameters for Bearer authentification
There are two forms of try_extract_params
helper function from
restinio::http_field_parsers::bearer_auth
that allows to extract parameters
for Bearer authentification scheme from
a HTTP-field:
#include <restinio/core.hpp>
#include <restinio/http_field_parser/bearer_auth.hpp>
...
auto on_request(const restinio::request_handle_t & req) {
using namespace restinio::http_field_parsers::bearer_auth;
const auto auth_params = try_extract_params(*req,
restinio::http_field::authorization);
if(auth_params) { // Parameters successfully extracted.
if(is_valid_user(auth_params->token)) {
...
}
}
...
}
If it is necessary to extract authentification/authorization parameters from a
HTTP-field with a custom name then another overload of try_extract_params
can be used:
#include <restinio/core.hpp>
#include <restinio/http_field_parser/bearer_auth.hpp>
...
auto on_request(const restinio::request_handle_t & req) {
using namespace restinio::http_field_parsers::bearer_auth;
const auto auth_params = try_extract_params(*req,
"X-My-Custom-Authorization");
if(auth_params) { // Parameters successfully extracted.
if(is_valid_user(auth_params->token)) {
...
}
}
...
}
Handling several authentification schemes
The examples above show how to work with just one authentification scheme.
If a server should support several schemes then it can be done with
overloads of try_extract_params
for Basic and Bearer schemes introduced
in v.0.6.8:
#include <restinio/core.hpp>
#include <restinio/http_field_parser/try_parse_field.hpp>
#include <restinio/http_field_parser/basic_auth.hpp>
#include <restinio/http_field_parser/bearer_auth.hpp>
...
auto on_request(const restinio::request_handle_t & req) {
using namespace restinio::http_field_parsers;
const auto field = try_parse_field<authorization_value_t>(
req, restinio::http_field::authorization);
if(const auto * auth = restinio::get_if<authorization_value_t>(field)) {
// We have valid Authorization field value.
if("basic" == auth->auth_scheme) {
// Basic authentification scheme should be used.
using namespace restinio::http_field_parsers::basic_auth;
const auto params = try_extract_params(auth->auth_params);
if(params) { // Parameters successfully extracted.
if(is_valid_user(params->username, params->password)) {
...
}
}
...
}
else if("bearer" == auth->auth_scheme) {
// Bearer authentification scheme should be used.
using namespace restinio::http_field_parsers::bearer_auth;
const auto params = try_extract_params(auth->auth_params);
if(auth_params) { // Parameters successfully extracted.
if(is_valid_user(auth_params->token)) {
...
}
}
...
}
else {
... // Handling of different schemes.
}
}
}