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 case enumerate_parts finishes its work and returns successful result;
  • handling_result_t::terminate_enumeration if the enumeration should be aborted. In that case enumerate_parts finishes its work and returns negative result with code enumeration_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 case enumerate_parts_with_files finishes its work and returns successful result;
  • handling_result_t::terminate_enumeration if the enumeration should be aborted. In that case enumerate_parts_with_files finishes its work and returns negative result with code enumeration_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.
      }
   }
}