Working with http headers

When using RESTinio one should work with two types of headers: request headers presented by restinio::http_request_header_t class and response header presented by restinio::http_response_header_t class.

The first one is used in read-only mode, while the second is mostly used in write-only mode, however, it also can be used for getting already specified values.

Both header classes mimic the structure of HTTP request/response header. They are derived from http_header_common_t which in turn is derived from http_header_fields_t. These classes expose the interface for working with common data in headers. The most important part is setters/getters for http header fields.

HTTP header fields

Working with most of the fields is done with http_header_fields_t class. Its brief interface is the following:

struct http_header_fields_t
{
    // ...
    using const_iterator = ...; // Some implementation-defined type.

    bool has_field( string_view_t field_name ) const noexcept;
    bool has_field( http_field_t field_id ) const noexcept;

    void set_field( http_header_field_t field );
    void set_field( std::string field_name, std::string field_value );
    void set_field( http_field_t field_id, std::string field_value );

    void append_field( string_view_t field_name, string_view_t field_value );
    void append_field( http_field_t field_id, string_view_t field_value );

    // Methods add_field are available since v.0.6.9.
    void add_field( http_field_t field_id, std::string field_value );
    void add_field( std::string field_id, std::string field_value );

    // Throws if the field is not found.
    const std::string & get_field( string_view_t field_name ) const;
    const std::string & get_field( http_field_t field_id ) const;

    // Throws if the field is not found.
    string_view_t value_of( string_view_t field_name ) const;
    string_view_t value_of( http_field_t field_id ) const;

    // Returns nullptr if the field is not found.
    nullable_pointer_t<const std::string> // It's just const std::string*
    try_get_field( string_view_t field_name ) const noexcept
    nullable_pointer_t<const std::string> // It's just const std::string*
    try_get_field( http_field_t field_name ) const noexcept

    // Returns empty optional if the field is not found.
    optional_t<string_view_t> opt_value_of( string_view_t field_name ) const noexcept;
    optional_t<string_view_t> opt_value_of( http_field_t field_id ) const noexcept;

    // Returns the default_value converted to std::string if the field is not found.
    std::string get_field_or(
        const std::string & field_name,
        /*some type*/ default_value ) const; // Some type can be const char*,
                                             // std::string, string_view.
    std::string get_field_or(
        http_field_t field_id,
        /*some type*/ default_value ) const; // Some type can be const char*,
                                             // std::string, string_view.

    // Since v.0.6.9 remove_field return `true` if something was removed.
    bool remove_field( const std::string & field_name );
    bool remove_field( http_field_t field_id );

    // Methods remove_all_of are available since v.0.6.9.
    // They return number of fields removed (a field with the same
    // name can be present several times).
    std::size_t remove_all_of( string_view_t field_name );
    std::size_t remove_all_of( http_field_t field_id );

    const_iterator begin() const noexcept;

    const_iterator end() const noexcept;

    auto fields_count() const noexcept;

    // This enumeration is available since v.0.6.9.
    enum class handling_result_t
    {
       continue_enumeration, stop_enumeration
    };

    // Invokes the specified lambda for each field.
    // Available since v.0.6.9.
    template< typename Lambda >
    void
    for_each_field( Lambda && lambda ) const;

    // Invokes the specified lambda for each value of the specified field.
    // Available since v.0.6.9.
    template< typename Lambda >
    void for_each_value_of( http_field_t field_id, Lambda && lambda ) const;
    template< typename Lambda >
    void for_each_value_of( string_view_t field_name, Lambda && lambda ) const;
    // ...
};

Each operation with a field has two versions of a function performing it: the one with referring field by its string name and another on referring a field by its enum element restinio::http_field_t that lists all that can be found here except Connection and Content-Length fields, because these fields have very special meaning and are handled explicitly with separate functions of http_header_common_t class. It is recommended to use restinio::http_field_t for manipulating with standard fields, while string referencing can be used for working with custom fields.

Usually, response header is used implicitly through one of response builder classes (Response builder) which has append_header() functions, and for most of the cases, it is enough to set all necessary header fields for a response.

Here is a sample example for using header fields:

restinio::request_handling_status_t handler( request_handle_t req )
{
    auto do_compression = [&]{
        if( 0< settings().compression_level() )
        {
            // Conmpression can be used, so check if it can be handled by client.
            const auto accept_encoding = req->header().get_field_of(
                    restinio::http_field::accept_encoding,
                    "gzip" );

            return settings().compression_is_possible( accept_encoding );
        }
        return false;
    };

    auto response_body = handling_logic( *req );

    auto resp = req->create_response();

    // Add 'Server' header field.
    resp.append_header( restinio::http_field::server, "RESTinio deleyed response server" );
    // Add 'Date' header field with current timestamp.
    resp.append_header_date_field();

    // Add custom fields.
    for( const auto & hf: settings().extra_headers() )
        resp.append_header( hf.first, hf.second );

    if( do_compression() )
    {
        // Add 'Content-Encoding' for compressed response body.
        resp.append_header( restinio::http_field::content_encoding, "gzip" );

        // Set compressed output here:
        resp.set_body( compress( response_body, settings().compression_level() ) );
    }
    else
    {
        resp.set_body( std::move( response_body ) );
    }

    return resp.done();
}

There are two special cases: Connection and Content-Length fields. http_header_common_t gives the following interface for them:

enum class http_connection_header_t : std::uint8_t
{
    keep_alive,
    close,
    upgrade
};

strut http_header_common_t
{
    // ...
    std::uint64_t content_length() const;
    void content_length( std::uint64_t l );

    bool should_keep_alive() const

    void should_keep_alive( bool keep_alive )

    //! Get the value of 'connection' header field.
    http_connection_header_t connection() const;

    //! Set the value of 'connection' header field.
    void connection( http_connection_header_t ch );
    // ...
};

As mentioned above normally one would use response builder for setting response header. So Connection and Content-Length are also manipulated through response builder. To close connection use resp_builder.connection_close() for keeping connection use resp_builder.connection_keep_alive(). Note: by default, value for connection field is set from the corresponding value of the original request. Content length is handled by response builders automaticaly, except for response_builder_t<user_controlled_output_t> (see User controlled output response builder).

Enumeration of fields and field’s values

Sometimes it’s necessary to enumerate all HTTP-fields from request’s. That enumeration can be performed by using different approaches.

The first one is ordinary for or range-for loops:

restinio::request_handling_status_t on_request(
   restinio::request_handle_t req )
{
   for( const auto & fld = req->header() )
   {
      logger_.trace( "field_name={}, field_value={}", fld.name(), fld.value() );
   }
   ...
}

The second approach is to use for_each_field template method:

restinio::request_handling_status_t on_request(
   restinio::request_handle_t req )
{
   req->header().for_each_field(
      [&logger_](const auto & fld) {
         logger_.trace( "field_name={}, field_value={}", fld.name(), fld.value() );
      } );
   ...
}

There could also be a need to enumerate all values (occurrences) of a field (please note that some fields can be repeated in a request/response). In that case a template method for_each_value_of can be useful:

restinio::request_handling_status_t on_request(
   restinio::request_handle_t req )
{
   headers().for_each_value_of(
      restinio::http_field_t::transfer_encoding,
      [](restinio::string_view_t value) {
         std::cout << "encoding: " << value << std::endl;
         return restinio::http_header_fields_t::continue_enumeration();
      } );
   ...
}

It is expected that for_each_value_of receives a functor that gets string_view as a single parameter and returns http_header_fields_t::handling_result_t. The return value of the functor is used to detect a need to continue enumeration. If the functor returns handling_result_t::stop_enumeration then enumeration will be stopped without the search for the next value and for_each_value_of returns.

Request header

Request header exposes the following extra interface:

struct http_request_header_t
{
    // ...
    http_method_t method() const;

    const std::string & request_target() const;

    string_view_t path() const;
    string_view_t query() const;
    string_view_t fragment() const;
};
  • http_request_header_t::method() returns http method of the request: http_method_t::http_get, http_method_t::http_post
  • http_request_header_t::request_target() returns request target (e.g. ‘/path’ from request to ‘http://mysever:8090/path’) .
  • functions path(), query(), fragment() return a string view object within request target representing the path, query and fragment part of the URL, e.g. for request target /weather/temperature?from=2012-01-01&to=2012-01-10#fahrenheit it will be /weather/temperature, from=2012-01-01&to=2012-01-10 and fahrenheit.

For handing query string parameter refer to Query string parameters section

Response header

The only important thing to mention is how status code and reason phrase are set. The standard way is to set them when creating response builder:

if( restinio::http_method_get() == req->header().method() &&
    req->header().request_target() == "/" )
{
    return req->create_response() // Default status_code=200; reason_phrase="OK"
        .append_header( restinio::http_field::server, "RESTinio" )
        .append_header_date_field()
        .append_header( restinio::http_field::content_type, "text/plain; charset=utf-8" )
        .set_body( "Hello world!")
        .done();
}

return req->create_response( 404, "Not found") // Set the status_code and reason_phrase.
    .append_header_date_field()
    .connection_close()
    .done();

Helpers for parsing values of HTTP-fields

Since v.0.6.1, RESTinio has a set of helpers for parsing values of HTTP-fields and working with parsed values. Those helpers can be found in restinio/helpers/http_field_parsers folder.

Please note that those helpers are not included in restinio/core.hpp header file. They should be included in your C++ file separately, for example:

#include <restinio/helpers/http_field_parsers/content-type.hpp>
#include <restinio/helpers/http_field_parsers/cache-control.hpp>

There is support for a few of standard HTTP-fields in RESTinio v.0.6.1. For example, helpers for Accept, Cache-Control, Content-Encoding, Content-Disposition, and Content-Type are implemented. Support for some other HTTP-fields will be added in the future REStinio’s versions. For the list of supported HTTP-fields see restinio/helpers/http_field_parsers folder.

Every helper looks like a struct with public fields in correspondence with the structure of specific HTTP-field. For example, for Content-Type, there is a struct like that:

namespace restinio {
namespace http_field_parsers {

struct content_type_value_t
{
   media_type_value_t media_type;

   static expected_t<content_type_value_t, restinio::easy_parser::parse_error_t>
   try_parse(string_view_t what);
};

} // namespace http_field_parsers
} // namespace restinio

And for Content-Encoding there is a struct like that:

namespace restinio {
namespace http_field_parsers {

struct content_encoding_value_t
{
   using value_container_t = std::vector<std::string>;

   value_container_t values;

   static expected_t<content_encoding_value_t, restinio::easy_parser::parse_error_t>
   try_parse( string_view_t what );
};

} // namespace http_field_parsers
} // namespace restinio

Such helpers can be used that way:

#include <restinio/core.hpp>
#include <restinio/helpers/http_field_parsers/content-type.hpp>

auto on_post_request(const restinio::request_handle_t & req) {
  namespace hfp = restinio::http_field_parsers;

  // Try to find Content-Type field.
  const auto content_type = req->header().opt_value_of(
        restinio::http_field::content_type);
  if(content_type) { // Content-Type is found.
     const auto parsed_value = hfp::content_type_value_t::try_parse(
           *content_type);
     if(parsed_value) { // Content-Type is successfuly parsed.
        if("text" == parsed_value->media_type.type &&
              "plain" == parsed_value->media_type.subtype) {
           ... // Handling text/plain representation.
               // Return positive response.
        }
        else if("application" == parsed_value->media_type.type &&
              "json" == parsed_value->media_type.subtype) {
           ... // Handling application/json representation.
               // Return positive response.
        }
     }
  }

  ... // Return negative response.
}

Parsing errors

Methods try_parse return an expected_t<T, parse_error_t> object that holds a valid value of type T in the case of successful parsing, or parse_error_t instance in the case of an error. Type parse_error_t is defined such way:

class parse_error_t
{
   // Position in the input stream.
   std::size_t m_position;
   // The reason of the error.
   error_reason_t m_reason;

public:
   // Initializing constructor.
   parse_error_t(
      std::size_t position,
      error_reason_t reason ) noexcept;

   // Get the position in the input stream where error was detected.
   [[nodiscard]] std::size_t
   position() const noexcept;

   // Get the reason of the error.
   [[nodiscard]] error_reason_t
   reason() const noexcept;
};

Where error_reason_t is an enum like that (note that actual content of that enum can be different in a particual version of RESTinio):

enum class error_reason_t
{
   // Unexpected character is found in the input.
   unexpected_character,
   // Unexpected end of input is encontered when some character expected.
   unexpected_eof,
   // None of alternatives was found in the input.
   no_appropriate_alternative,
   // Required pattern is not found in the input.
   pattern_not_found,
   // There are some unconsumed non-whitespace characters in the input
   // after the completion of parsing.
   unconsumed_input,
   // Illegal value was found in the input.
   illegal_value_found,
   // A failure of parsing an alternative marked as "force only this
   // alternative".
   //
   // This error code is intended for internal use for the implementation
   // of alternatives() and force_only_this_alternative() stuff.
   //
   // This error tells the parser that other alternatives should not be
   // checked and the parsing of the whole alternatives clause should
   // failed too.
   //
   force_only_this_alternative_failed
};

The values of that enumeration can be a bit cryptic at first glance. It’s because they are tightly coupled to underlying parsing mechanics. This mechanics is an experimental work inside RESTinio and we hope there is a lot of improvements to be implemented in such area with time.

There is also helper function make_error_description that allows to get a textual description of parsing error:

namespace hfp = restinio::http_field_parsers;

const auto content_type = req->header().opt_value_of(
      restinio::http_field::content_type);
if(content_type) { // Content-Type is found.
   const auto parsed_value = hfp::content_type_value_t::try_parse(
         *content_type);
   if(parsed_value) { // Content-Type is successfuly parsed.
      ... // Processing of parsed value.
   }
   else {
      log_error("unable to parse Content-Type field: ",
         hfp::make_error_description(parsed_value, *content_type));
   }
}

Note. The output format of make_error_description is not specified and can be changed in the future versions of RESTinio.

find_first helper functions

Some HTTP-fields contain a list of parameters with mandatory (like for Content-Type) or optional values (like for Cache-Control). RESTinio provides a couple of find_first helper functions to simplify search of a particular value. For example:

namespace hfp = restinio::http_field_parsers;

const auto content_type = req->header().opt_value_of(
      restinio::http_field::content_type);
if(content_type) { // Content-Type is found.
   const auto parsed_value = hfp::content_type_value_t::try_parse(
         *content_type);
   if(parsed_value) { // Content-Type is successfuly parsed.
      // We have to analyze charset parameter.
      const auto charset = hfp::find_first(
            parsed_value->media_type.parameters, "charset");
      if(charset) { // charset is found.
         ... // Do something with charset value.
      }
   }
}

There are two variants of find_first function. The first returns expected_t<string_view_t, not_found_t> and indended to be used for searching for parameters with mandatory values. The second returns expected_t<optional_t<string_view_t>>, not_found_t> and intended to be used for lists of parameters with optional values.

Both versions return an instance of empty not_found_t type in the case if the specified parameter is not found.

Both versions perform case-insentive search.