Sessions and server side state

Introduction

Different scopes for data and services

The server-side state of an application refers to data stored on server side that could be shared by all clients, or that could be specific to a limited scope such as:

  • the site
  • a session,
  • a group of sessions (for example all sessions of the same user),
  • a client-side process (when programming an Eliom app), or
  • the current request.

For a given scope, server-side state consists of:

  • services registered with this specific scope, and
  • data encapsulated within an Eliom reference created with this specific scope.
  • Bus, channels, etc. (actually implemented using services and Eliom references)

States disappear either when the associated scope is discarded explicitely or by a timeout.

Scope and cookies

On a technical point of view, sessions and groups of sessions are implemented automatically within Eliom by asking the browser to send a session identifier in a cookie. Client-side processes also send an identifier in each request, using some kind of "client-side process-cookie".

It is possible to create different scopes of the same level if you want several states for the same application (advanced use). Each scope uses its own cookies and you can discard data for one scope only.

Secure cookies

States can be secure or not. Secure means that the state data or service will be associated to a secure cookie, that is a cookie that is sent by the browser only if the protocol is HTTPS. Use secure states to access confidential data, that you do not want to send through HTTP.

Three kind of states

In the current implementation, because of some limitation in OCaml's serialization mechanism, there are three kinds of states (for each scope):

  • volatile data states,
  • volatile service states,
  • persistent data states.

Volatile states will not survive after relaunching the server. There is no persistent service state. Be very careful if you use both persistent state data and service state, as your session may become inconsistent (use service states only for volatile services, like coservices with timeouts).

We hope to simplify this when OCaml's serialization mechanism evolves. In the meantime, be very careful where you store your data. To avoid shutting down the server, note that it is possible to ask the server to load dynamically new versions of your site (see command reload ).

Scopes, cookies and sessions

Basics

Eliom uses a notion of scopes to restrict the visibility of server-side data to some clients. Together with Eliom references, this is a very convenient way to implement a mechanism of session for your website.

There are two categories of scopes:

Within the users scopes, Eliom distinguishes three scopes that differ on how Eliom associates clients and data.

User scopes are organised in hierarchy: client processes belong to a session, and sessions belong to a group of sessions.

Data and dynamic services with a user scope can be discarded explicitely or by timeout. See sections Closing session and Timeouts and session duration for more information.

If you want to handle multiple sessions for the same site — e.g. several different data sessions that could be created and discarded independently — you can create new users scope that will use different cookies, see section Hierarchies of scopes for more information.

Creating sessions and scopes

Automatic session creation

Eliom automatically creates a session — and sets the corresponding cookie on the client — when you first modify an Eliom reference of scope session, when you register a service with this scope or when you enter a session group.

By default, Eliom is using three cookies for sessions (and session groups):

  • one for session services,
  • one for volatile session data,
  • one for persistent session data.

For client side processes, it uses the same three kinds of client side process cookies.

Hierarchies of scopes and multiple sessions (advanced use)

If you want to handle multiple sessions for the same site that could be created and discarded independently, you can create a new hierarchy of users scope that will use different cookies, with the function Eliom_common.​create_scope_hierarchy.

let custom_session_hierarchy = Eliom_common.create_scope_hierarchy "custom"
let custom_session = `Session custom_session_hierarchy

Then, the value custom_session could replace the usual Eliom_common.​default_session_scope for the ~scope parameter of functions Eliom_reference.​eref, Eliom_registration.​Html5.​register, Eliom_state.​discard, ...

(Same for `Client_process or `Session_group scope levels).

The function Eliom_common.​create_scope_hierarchy will prevent you to create two scope hierarchies with the same name.

Example of use:

A typical use of hierarchies is to have one hierarchy for connected users (the default hierarchy), and one independent from connection. Use the first one to save user related content. It will be discarded when the user logs out. Use the second one to save data corresponding to one tab or browser, (the user being connected or not), for example because this data must be available before the user logs in.

Closing sessions (and other states)

To discard a state, use the Eliom_state.​discard function. It will remove all server-side services and data (persistent or not) for the given scope. Used with ~scope:Eliom_common.default_session_scope, this will close a session. Used with ~scope:Eliom_common.default_group_scope, this will close all sessions in the group.

It is also possible to discard selectively only services, persistent data or volatile data (see the Eliom_state module). But this may be periculous. Be very careful when doing this, as you are desynchronizing the three kinds of sessions.

Warnings:

  • It is a good idea to close the session when a user tries to connect, even if it is already connected.
  • You may also want to unset some request-scoped Eliom references when discarding a state,
  • If your state data contains opened file descriptors, they won't be closed by OCaml's garbage collector. Close them yourself! (for example using Gc.finalise).

Timeouts and session duration

The default timeout for sessions in one hour. Sessions will be automatically closed after that amount of time of inactivity from the user. You can change that value for your whole site using the Eliom_state.​set_global_volatile_state_timeout.

There is also a possibility to change the default value for Eliom in the configuration file like this:

<extension findlib-package="ocsigenserver.ext.eliom">
  <volatiletimeout value="7200"/>
</extension>

In the configuration files the value "infinity" means no timeout.

That default may be overriden by each site using Eliom_state.​set_global_volatile_state_timeout or Eliom_state.​set_default_volatile_state_timeout. If you want your user to be able to set the default in the configuration file for your site (between <site> and </site>), you must parse the configuration (using Eliom_config.​get_config function). You can also change the timeout for a specific user only with the following functions: Eliom_state.​set_volatile_data_state_timeout. For more details, see the Eliom_state module's interface.

Secure session

By default, data and services saved in a session are available to requests using both http or https protocols. If you want to keep some state in a secure session that are visible only to client accessing with the https protocol, you may give the optional parameter ~secure:true when calling functions like Eliom_reference.​eref, Eliom_registration.​Html5.​register, etc.

Secure sessions are using secure cookies (i.e. Ocsigen Server will ask the browsers to send the cookie only if the protocol is HTTPS). Thus it is not possible to access secure references and services if the user is using http.

Session groups

Sharing data between a group of sessions

Session group is a kind of scope that allows sharing data or services between a set of sessions, typically all sessions for given user. For example, using persistent Eliom references with scope Eliom_common.​default_group_scope is a convenient way to store data about a user without having to explicitly use an external database. (Persistent session group states are not discarded when all the sessions are closed).

A session group is identified by a name. The current session could be attached to a group of sessions using one of the following functions — depending on the nature of the data you want to share. They take the session group name as parameter:

A session could be only attached to one group at a time, but it is possible to create multiple sessions for a same client attached to different groups, see section Hierarchies of scopes for more information.

It's possible to fetch the current session group name of a session, if any, or to detach a session from a group. See the module Eliom_state for more information.

Limit the number of session within a group

The number of sessions in a group is limited. If all sessions are in a group you will avoid some malicious user to open too many sessions. If you do not use session groups, the number of sessions is limitated by IP address, which can be a problem for example if the server is behind a reverse proxy or if many user are using the same NAT. That's why we always recommend to set the session group (usually it's the user name or id).

Close all session of a group

Session groups allows the implementation of features like "close all sessions" for one user (even those opened on other browsers). Consider the following scenario: a user logs in from home using a "Remember me on this computer" feature, which sets a (almost) no-expiration cookie on his browser and session timeouts of infinity on the server. The user goes on vacation, and while logging from a cyber-café, he also sets the "Remember me" option. Back home he realises the mistake, and wishes to do a "global logout", i.e., closing all existing sessions associated with his user name.

For those kind of usage, it is highly recommended to set a group for each of the three kinds of states you use (services, volatile data and persistent data).

To close all sessions from a group, close the group.

See section Closing sessions for more information.

Eliom references

Principles

Eliom references (erefs) are some kind of references with a (possibly) limited scope. You declare a reference with an initial value and a scope. Then, when you change the associated value, it actually changes only for the scope you specified.

Eliom references can be persistent or not (that is: can survive after relaunching the server or not).

Eliom references are used for example:

  • to store session data, server side data for a client process, or user data (scope: session, client process, session group),
  • or to keep some information about the current request (scope: request), for example to give information to the service taking in charge the request after an action,
  • to implement persistent references (scope: global)
  • for caching functions (Eliom_reference.​eref_from_fun).

Non persistent global Eliom references are equivalent to regular OCaml references.

Eliom references are either created using the function Eliom_reference.​eref, that works like the usual Ocaml ref function, but with at least one additional scope parameter. Or they may be created by the function Eliom_reference.​eref_from_fun: Its argument function is evaluated the first time the reference is accessed (through Eliom_reference.​get), within one scope or after the reference has been reset.

The Eliom_reference module also defines functions to get the value, set, modify it (by applying a function to its content), and unset it, this is reset to the initial value.

Persistent references

Persistent references are Eliom references that survives after relaunching the server. They are implemented using the Ocsipersist module for which Ocsigenserver provides two implementations, one based on SQLite, the other one based on DBM.

Persistent references are created by adding the ~persistent parameter to the Eliom_reference.​eref function calls. The value of this parameter is the name of the reference in the database.

Volatile references

The module Eliom_reference.​Volatile allows the creation of non-persistent Eliom references, which can then be used through a non-Lwt interface.

As Eliom_reference.​Volatile.​eref is a subtype of Eliom_reference.​eref, a volatile reference eref may be used as (eref : _ Eliom_reference.eref) with the Lwt-interface of Eliom_reference alike.

Accessing other states

Sometimes, it is useful to access other states. For example if you want to send a notification to another user, you may want to find the communication channel registered for this user. It can probably be found as an Eliom reference in the group corresponding to this user.

Use module Eliom_state.​Ext to get the state corresponding to a group name. Use Eliom_state.​Ext.​iter_sub_states to iterate on all sessions in a group, or on all client processes in a session.

Use Eliom_reference.​Ext to access Eliom references belonging to another state.

Low-level cookies manipulation

Eliom references are used to store data on the server-side. It is also possible to ask the browser or the client-side process to record some piece of data and send it back to the server with each request.

This is implemented using the usual browser cookies for sessions — and a simulation of browser cookies by Eliom client side processes — with the functions Eliom_state.​set_cookie and Eliom_state.​get_cookie.