C++ REST SDK sessions

Tags: , ,

This article describes and provides an implementation of sessions in C++ REST SDK (Casablanca).

Sessions are used to manage user roles and permissions. They can be used as an interface to manage “session data” or may be a way of clustering services.

This article may be helpful for building an authentication service in Casablanca.

Resources

The provided code has been tested on Linux and MacOS X.
The given examples use native c++ objects to store data, I did it this way in case you don't want to use external libraries. In case you would like to use Redis to store data I included classes that give you this possibility, to integrate it I used redisclient by Alex Nekipelov.

Schema

Session classes C++ REST SDK

Sessions are unique, they are stored and retrieved thanks to a token passed through a cookie, a JSON or a query string.

All sessions inherit from a Session abstract class.

Their life cycle and storage is managed by a Session Handler. The basic life of a session consists of three steps: Open, Update and Close. Session handlers implement the SessionHandler interface.

All the objects are configured thanks to configuration properties, I use a configuration file for storing them but as configuration properties are accessed through the Session Handler, you can use other support as c++ constants to store them.

Code

Retrieving session:

void TestController::handle_post(web::http::http_request request)
{
  web::http::http_response response;
  granada::http::session::MapSession storage_session(request,response);
  //  ...
}

If you are using a cookie to store the token and session does not exist or is timed out, a new session will be opened.

If you are using JSON or query strings to pass the token, this is the way of retrieving, opening and closing a session:

// Retrieve session if it exists, if it does not, no session is created
granada::http::session::MapSession storage_session(request,response);

// check if a session is opened testing its token
if (storage_session.GetToken().empty()){

  // session does not have a token, session is not opened
  // open it.
  storage_session.Open();

}else{

  // session is already opened

}

// close the session.
storage_session.Close();

In this example MapSession internally uses a Session Handler (MapSessionHandler) to retrieve the stored session values, as well as to update and close itself.

Session Roles

Sessions may have roles, roles are used to manage user permissions, for example letting or not the user to access some data.

Different sessions may use different role implementations. Session Role classes implement the Roles interface, so you can implement them as you want, in the given examples roles and role properties are stored in a map but you may want to implement a role class that use for example a database to store roles and its properties.

// adding USER role
storage_session.roles()->Add("USER");

// setting role property
storage_session.roles()->SetProperty("USER","username",username);

// retrieve role property
std::string username = storage_session.roles()->GetProperty("USER","username");

// destroy role property
storage_session.roles()->DestroyProperty("USER","username");

// check session roles
if (storage_session.roles()->Is("USER")){
  // User has the permission to access data.
}else{
  // cannot access data.
}

// remove role
storage_session.roles()->Remove("USER");

// at this point user won't be able to access data.

This way we can generate a token so other applications can access our user data, we can do it this way:

void AuthController::handle_post(web::http::http_request request)
{

  // Open a session an deliver the token to the client
  // so any application using this token can read user
  // messages.

  web::http::http_response response;
  granada::http::session::MapSession simple_session(request,response);

  // open the session. It will create a token and store the session.
  simple_session.Open();

  // Add the role that will give the user the privilege of reading
  // messages.
  simple_session.roles()->Add("MESSENGER_READER");

  // deliver the session token to the user client so he can share
  // it with other applications that will read his messages.
  response.set_body("{\"token\":\"" + simple_session.GetToken() + "\"}");
  response.set_status_code(status_codes::OK);
  response.headers().add(U("Content-Type"), "text/json; charset=utf-8");
  request.reply(response);
}

Any client using this token will now have the role MESSENGER_READER and the permission to read the user messages.

Session Factory

These classes apart from being useful for instantiating sessions can be used for centralizing the sessions setting, which in most cases means “check if session exists and is valid and create a new one if it is not”. As session factories implement Session Factory we can benefit from polymorphism and “check” our sessions without knowing their type.

Session data

All sessions inheriting from Session can manage “session data”.
Different sessions may use different data storage implementations. The data storage manager classes inherit from the CacheHandler abstract class, they are called drivers, in the implemented libraries you can see there is a SharedMapCacheDriver that stores data in a map shared by all sessions and a RedisCacheDriver that stores data using Redis data structure server.

// store some session data, store a key-value pair
storage_session.Write("test","hello world!!!");

// retrieve session data
std::string my_var = storage_session.Read("test");
// my_var is equal to “hello world!!!”

// destroy session data
storage_session.Destroy("test");
I have not included the CacheHandler or Cache drivers in the schema above as it is not really part of the session architecture.

Conclusions

Concurrency

Due to C++ REST SDK design we can have multiple threads using the same session. So be careful when opening, deleting, modifying roles or session data, the last modification will be the one stored.

If your client makes asynchronous calls, take care of using a session factory to centralize the initialization of the session at the beginning of the application (as it is done in the provided cart example), so only one session-token is created.

Using Redis

I recommend using Redis to store session data or even sessions. Redis has a lot of features already implemented that can be very powerful and will save us lots of time.

Next steps

As I have already mentioned, sessions will help us to implement an authentication service in Casablanca. I would also find interesting the implementation of custom user services clustered by sessions.


Please contact if you want to discuss about the code:
- Twitter
- Mail: afernandez [at] cookinapps.io

Standard structure for C++ REST SDK servers

Tags: , ,

This article proposes a unified standard structure for C++ REST SDK server applications. It solves the problem of user permissions using sessions and the problem of user data storage. It is a typed architecture based on the BlackJack server example included in the cpprest kit.

Resources

  • You can find the implementation of the explained architecture here (Tested on linux and MacOS X).
  • C++ REST SDK.

Schema

c++ REST SDK structure

Configuration file

The configuration file contains all the properties for configuring our objects, for example it contains the address and port where the server is going to listen, the session configuration, database connection properties etc.

The configuration file is parsed when any property is asked for the first time. The properties are accessible anywhere in the application, so we can include non native properties used in our project declared classes.

Example of configuration file:

# server address, will be http://localhost by default
address=http://192.168.1.9
# port default will be 80
port=80
# path extra uri path, will be empty by default, example: http://localhost:80/extrauripath

# Path of the directory containing the website files if it is not set or it doesn't exist,  ./www/ path will be taken, if this does not exist ./ will be taken.
# For relative paths use ./name_of_directory
root_path=www
default_files=["index.html","index.htm"]
error_paths={"404":"404/"}

# timeout
# time that has to pass since the last session use until
# the session is removed in seconds.
# 1 day = 86400
session_timeout=-1
session_clean_frequency=-1
session_clean_extra_timeout=0

Comments are declared with a # at the beginning of the line.
Properties are declared with a key and value separated by an = character.

You can see a full configuration file here.

Controllers

Controllers asynchronously handle the HTTP requests and give a response to the client.

They can deal with four HTTP methods, GET, POST, PUT and DELETE, each one will be canalized into its respective function: handle_get(http_request), handle_post(http_request), handle_put( http_request) and handle_delete(http_request).

All controllers inherit from a Controller abstract class.

Example of controller:

#include "granada/http/controller/controller.h"

namespace granada{
  namespace http{
    namespace controller{
      class ExampleController : public Controller {
        public:

          /**
           * Constructor
           */
          ExampleController(utility::string_t url);

        private:

          /**
           * Handles HTTP GET requests.
           * @param request HTTP request.
           */
          void handle_get(http_request request);


          /**
           * Handles HTTP PUT requests.
           * @param request HTTP request.
           */
          void handle_put(http_request request);


          /**
           * Handles HTTP POST requests.
           * @param request HTTP request.
           */
          void handle_post(http_request request);


          /**
           * Handles HTTP DELETE requests.
           * @param request HTTP request.
           */
          void handle_delete(http_request request);
      };
    }
  }
}

Implementation of post requests handle function:
void ExampleController::handle_post(web::http::http_request request)
{

  web::http::http_response response;

  // (...)

  request.reply(response);

}

Session

Sessions are used to manage user roles and permissions. Some can store “session data” or may be a way of clustering services.

Sessions are unique, stored until used again, they are retrieved thanks to a token passed through a cookie, a JSON or a query string.

All sessions inherit from a Session abstract class.

Their life cycle and storage is managed by a Session Handler. The basic life of a session consists of three steps: Open, Update and Close.

Business classes

Where all the application intelligence is. A simple example of business class is a cart that manages the storage of products and quantities and retrieves them taking care of user permissions.

Data/ Resources access and management

These classes are used to store and manage data and resources. They can be used for caching web resources, to store session data etc.

"Cache" classes implement a CacheHandler interface. They are called drivers, and they can store data in different supports: a map, a shared map, Redis etc. Some persist data, other do not.

Their basic methods are: Write data, Read data and Destroy data entry.

We have to be careful and destroy data entries that won't be used in the future.