Extra-data in request object

Sometimes it’s necessary to associate some additional (extra) data with a request object. For example, after authentication, a user-id is produced and that user-id has to be bound with the request during the whole request processing. It is not a simple task especially if the processing is divided into several stages and those stages are invoked separately (by using sync-chains, for example).

There wasn’t a “just out of the box” solution for that problem until v.0.6.13. But starting from v.0.6.13 it is possible to incorporate arbitrary user-defined type into a request object.

Extra-data in request object

So if we want to associate a user-id with a request we can do the following:

Define a type with an arbitrary name that will serve as a factory:

struct my_extra_data_factory {
   ...
};

That type should contain a type or alias with name data_t. In our simple example it can be an alias for some user_identity type:

struct my_extra_data_factory {
   using data_t = user_identity;
   ...
};

Our my_extra_data_factory should also contain a method make_within like that:

struct my_extra_data_factory {
   using data_t = user_identity;

   void make_within(restinio::extra_data_buffer_t<data_t> buf) {
      new(buf.get()) data_t{};
   }
};

After that we have to specify our my_extra_data_factory as user_data_factory_t in server’s traits:

struct my_traits : public restinio::default_traits_t {
   using extra_data_factory_t = my_user_data_factory;
};

Now, if we run a sever this our my_traits it will create a new instance of restinio::generic_request_t< my_extra_data_factory::data_t >. That instance will hold an instance of my_extra_data_factory::data_t inside. And we can access that instance by using extra_data() method:

restinio::request_handling_status_t my_req_handler(
   restinio::generic_request_handle_t<my_extra_data_factory::data_t> req)
{
   auto & ud = req->extra_data();
   ...
}

NOTE. If we define our custom extra-data-factory then we can’t use simple names like restinio::request_t and restinio::request_handle_t anymore, we have to use templates restinio::generic_request_t and restinio::generic_request_handle_t.

The role of extra-data-factory

Why should we define extra-data-factory type and can’t simply specify our data type (like user_identity from the example above) in server’s traits?

It’s because there can be cases when extra-data type won’t be DefaultConstructible and we have to initialize a new extra-data instance with some parameters.

For example, suppose we want to create a new log-stream for every incoming request for tracking all request-related actions separately. In that case our request-specific extra-data can look like:

struct per_request_data {
   std::shared_ptr<log_stream> log_;

   per_request_data(std::shared_ptr<log_stream> log)
      : log_{std::move(log)}
   {}
};

We can’t just create a new instance of per_request_data without specifying a pointer to log-stream.

To cope with such a problem we need a stateful factory for new per_request_data objects:

class my_extra_data_factory {
   std::shared_ptr<logger> logger_;
public:
   using data_t = per_request_data;

   my_extra_data_factory(std::shared_ptr<logger> logger)
      : logger_{std::move(logger)}
   {}

   void make_within(restinio::extra_data_buffer_t<data_t> buf) {
      new(buf.get()) data_t{
         std::make_shared<log_stream>(logger_)
      };
   }
};

Then we have to create an instance of that factory and pass that instance to server’s settings:

struct my_traits : public restinio::default_traits_t {
   using extra_data_factory_t = my_user_data_factory;
};

auto logger = std::make_shared<logger>(...);

restinio::run(restinio::on_thread_pool<my_traits>(16)
   .port(...)
   .address(...)
   .extra_data_factory(std::make_shared<my_user_data_factory>(logger))
   .request_handler(...)
);

Now the instance of my_extra_data_factory will create properly initialized objects of type per_request_data.

Is extra-data-factory DefaultConstructible or not?

If extra-data-factory is DefaultConstructible then there is no need to create an instance of it and pass that instance to setting’s extra_data_factory() method manually. A new instance will be created automatically during the initialization of the server.

But if extra-data-factory isn’t DefaultConstructible (and in most cases stafeful extra-data-factories aren’t) then a user has to create an instance of extra-data-factory manually and pass a shared-pointer to it into setting’s extra_data_factory() method.