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.