Getting started

To start using RESTinio make sure that all dependencies are available. The tricky one is nodejs/http-parser, because it is a to be compiled unit, which can be built as a static library and linked to your target or can be a part of your target. And it is worth to mention that asio that is used by RESTinio is the one from github repository.

To start using RESTinio simply include <restinio/all.hpp> header. It includes all necessary headers of the library.

It easy to learn how to use RESTinio by example.

Minimalistic hello world

Here is a minimal hello world HTTP server (see full example):

#include <restinio/all.hpp>

int main()
{
  restinio::run(
    restinio::on_this_thread()
      .port(8080)
      .address("localhost")
      .request_handler([](auto req) {
        return req->create_response().set_body("Hello, World!").done();
      }));

  return 0;
}

Here a helper function restinio::run() is used to create and run the server. It is the easiest way to start the server, it hides some boilerplate code for simple common cases. RESTinio considers the following two typical cases:

  • run server on current thread;
  • run server on thread pool.

See details here.

Each restinio::run() function creates HTTP server instance with specified settings and runs it. And for also it subscribes to breakflag signals to stop the server when ctrl+c is hit.

RESTinio does its networking stuff with asio library, so to run the server it must have an asio::io_context instance to run on. Each restinio::run() function creates an instance of asio::io_context and then runs it (via asio::io_context::run() function) on a current thread or on a thread pool.

restinio::run() functions receive server settings as an argument. Server settings is a fluent interface class for various server options. Most of the settings have reasonable default values. And one setting is especially important: it is request_handler. It is a function-object that handles HTTP requests. In the sample above it is lambda that serves all request with “Hello, World!” response. Other two option in the sample specify to run the server on localhost and listen for connections on port 8080.

Enhance request handler

Let’s see a more complicated request handler and look at what it does.

Let’s use the following function as a request handler:

restinio::request_handling_status_t handler(restinio::request_handle_t req);
{
  if( restinio::http_method_get() == req->header().method() &&
      req->header().request_target() == "/" )
  {
    req->create_response()
      .append_header( restinio::http_field::server, "RESTinio hello world server" )
      .append_header_date_field()
      .append_header( restinio::http_field::content_type, "text/plain; charset=utf-8" )
      .set_body( "Hello world!")
      .done();
    return restinio::request_accepted();
  }
  return restinio::request_rejected();
}

int main()
{
  restinio::run(
    restinio::on_this_thread()
      .port(8080).address("localhost")
      .request_handler(handler));

  return 0;
}

handler() Function has a single parameter that holds a handle on actual request - restinio::request_t that has the following API (the details are omitted):

class request_t // Base class omitted.
{
  public:
    const http_request_header_t & header() const;

    const std::string & body() const;

    template < typename Output = restinio_controlled_output_t >
    auto create_response( http_status_line_t status_line = status_ok() );
};
  • request_t::header() gives access to http_request_header_t object that describes HTTP header of a given request;
  • request_t::body() gives access to body of a given request;
  • request_t::create_response() creates an object for cunstructing and sending request.

Two first functions are rather straightforward, so there is no much sense to go into more details on them. A more intriguing function is request_t::create_response() - it is a template function that is customized with Output type. For now it would be enough to use the default customization. request_t::create_response() has one parameter status line (HTTP response code + HTTP response reason phrase). By default, these params are set for HTTP/1.1 200 OK response. If called for the first time request_t::create_response() returns an instance of type response_builder_t<Output>, it is an object for constructing and sending response on a given request. To avoid the possibility of creating multiple responses on a single request request_t::create_response() will return an object only for the first call all further call would throw.

For setting response in the sample above we use some functions of a response builder:

  • append_header(field, value) - for setting header fields;
  • append_header_date_field() - for setting Date field value with current timmestamp;
  • set_body() - for setting response body.

All of the mentioned functions return the reference to a response builder that gives some syntactic sugar and allows to set response in a nice way.

For sending a response to peer a done() function is used. It stops constructing response (no further setters will have an effect on the response) and initiates sending a response via an underlying TCP connection.

One thing left to mention is what request handler returns. Any request handler must return a value of request_handling_status_t enum:

enum class request_handling_status_t
{
  accepted,
  rejected
};

There are two helper functions: restinio::request_accepted() and restinio::request_rejected() for referring the items of the enum. Both of them are used in the sample.

See also a full sample.

Enhance request handler even more

Here we will add basic routing for incoming requests. RESTinio has express-like request handler router (see Express router for more details).

The signature of a handler that can be put in a router has an additional parameter – a container with parameters extracted from URI.

Let’s see how we can use express-router in a sample request handle:

using router_t = restinio::router::express_router_t;

auto create_server_handler()
{
  auto router = std::make_unique< router_t >();

  router->http_get(
    "/",
    []( auto req, auto ){
        init_resp( req->create_response() )
          .append_header( restinio::http_field::content_type, "text/plain; charset=utf-8" )
          .set_body( "Hello world!")
          .done();

        return restinio::request_accepted();
    } );

  router->http_get(
    "/json",
    []( auto req, auto ){
        init_resp( req->create_response() )
          .append_header( restinio::http_field::content_type, "text/json; charset=utf-8" )
          .set_body( R"-({"message" : "Hello world!"})-")
          .done();

        return restinio::request_accepted();
    } );

  router->http_get(
    "/html",
    []( auto req, auto ){
        init_resp( req->create_response() )
            .append_header( restinio::http_field::content_type, "text/html; charset=utf-8" )
            .set_body(
              "<html>\r\n"
              "  <head><title>Hello from RESTinio!</title></head>\r\n"
              "  <body>\r\n"
              "    <center><h1>Hello world</h1></center>\r\n"
              "  </body>\r\n"
              "</html>\r\n" )
            .done();

        return restinio::request_accepted();
    } );


  return router;
}

int main()
{
   using traits_t =
      restinio::traits_t<
         restinio::asio_timer_manager_t,
         restinio::single_threaded_ostream_logger_t,
         router_t >;

   restinio::run(
      restinio::on_this_thread<traits_t>()
         .port( 8080 )
         .address( "localhost" )
         .request_handler( create_request_handler() ) );
}

Function create_server_handler() creates an instance of restinio::router::express_router_t with three endpoints:

  • ‘/’ - default path: reply with text hello message;
  • ‘/json’: reply with json hello message;
  • ‘/html’: reply with html hello message.

Note in the sample above we do not use route parameters.

Considering the implementation of create_server_handler() above, we can notice that it return a unique pointer on a router class. And it is not a function object. So how RESTinio can use it? To receive an accurate answer one should read Basic idea section first.

A brief and non-accurate answer will be that “RESTinio” is customizable for concrete types of a handler, and if it knows a concrete type of a handler it can receive it wrapped in a unique pointer. To set this (and not only this) customization a traits class is used. In samples in previous sections default traits were used, so they were hidden. In this sample we explicitly define traits to use:

using router_t = restinio::router::express_router_t;

using traits_t =
  restinio::traits_t<
    restinio::asio_timer_manager_t,
    restinio::single_threaded_ostream_logger_t,
    router_t >;

Here we use a helper class restinio::traits_t that has some customizations defined by default. Request handler type is on the third place.

And to run the server, we need to point the traits we are using:

restinio::run(
  restinio::on_this_thread<traits_t>() // Use custom traits.
    .port( 8080 )
    .address( "localhost" )
    .request_handler( create_request_handler() );

Function restinio::on_this_thread<Traits>() involves a creation of a special type and from this type restinio::run() function deduces the trats for its server.

See also a full sample.