Data::Session(3pm)

โœจ NAME โœจ

Data::Session(3pm) โ€” Data::Session - Persistent session data management


๐ŸŒธ Synopsis ๐ŸŒธ

okay so bestie, here's the tea โ˜•. seven examples, all the vibes. ๐Ÿ’…

  1. A self-contained CGI script (scripts/cgi.demo.cgi):
#!/usr/bin/perl

use CGI;

use Data::Session;

use File::Spec;

# ----------------------------------------------

sub generate_html
{
        my($name, $id, $count) = @_;
        $id        ||= '';
        my($title) = "CGI demo for Data::Session";
        return     <<EOS;
<html>
<head><title>$title</title></head>
<body>
        Number of times this script has been run: $count.<br/>
        Current value of $name: $id.<br/>
        <form id='sample' method='post' name='sample'>
        <button id='submit'>Click to submit</button>
        <input type='hidden' name='$name' id='$name' value='$id' />
        </form>
</body>
</html>
EOS

} # End of generate_html.

# ----------------------------------------------

my($q)        = CGI -> new;
my($name)     = 'sid'; # CGI form field name.
my($sid)      = $q -> param($name);
my($dir_name) = '/tmp';
my($type)     = 'driver:File;id:MD5;serialize:JSON';
my($session)  = Data::Session -> new
(
        directory => $dir_name,
        name      => $name,
        query     => $q,
        type      => $type,
);
my($id) = $session -> id;

# First entry ever?

my($count);

if ($sid) # Not $id, which always has a value...
{
        # No. The CGI form field called sid has a (true) value.
        # So, this is the code for the second and subsequent entries.
        # Count the # of times this CGI script has been run.

        $count = $session -> param('count') + 1;
}
else
{
        # Yes. There is no CGI form field called sid (with a true value).
        # So, this is the code for the first entry ever.
        # Count the # of times this CGI script has been run.

        $count = 0;
}

$session -> param(count => $count);

print $q -> header, generate_html($name, $id, $count);

# Calling flush() is good practice, rather than hoping 'things just work'.
# In a persistent environment, this call is mandatory...
# But you knew that, because you'd read the docs, right?

$session -> flush;
  1. A basic session. See scripts/sqlite.pl:
# The EXLOCK is for BSD-based systems.
my($directory)   = File::Temp::newdir('temp.XXXX', CLEANUP => 1, EXLOCK => 0, TMPDIR => 1);
my($data_source) = 'dbi:SQLite:dbname=' . File::Spec -> catdir($directory, 'sessions.sqlite');
my($type)        = 'driver:SQLite;id:SHA1;serialize:DataDumper'; # Case-sensitive.
my($session)     = Data::Session -> new
(
        data_source => $data_source,
        type        => $type,
) || die $Data::Session::errstr;
  1. Using BerkeleyDB as a cache manager. See scripts/berkeleydb.pl:
# The EXLOCK is for BSD-based systems.
my($file_name) = File::Temp -> new(EXLOCK => 0, SUFFIX => '.bdb');
my($env)       = BerkeleyDB::Env -> new
(
        Home => File::Spec -> tmpdir,
        Flags => DB_CREATE | DB_INIT_CDB | DB_INIT_MPOOL,
);
if (! $env)
{
        print "BerkeleyDB is not responding. \n";
        exit;
}
my($bdb) = BerkeleyDB::Hash -> new(Env => $env, Filename => $file_name, Flags => DB_CREATE);
if (! $bdb)
{
        print "BerkeleyDB is not responding. \n";
        exit;
}
my($type)    = 'driver:BerkeleyDB;id:SHA1;serialize:DataDumper'; # Case-sensitive.
my($session) = Data::Session -> new
(
        cache => $bdb,
        type  => $type,
) || die $Data::Session::errstr;
  1. Using memcached as a cache manager. See scripts/memcached.pl:
my($memd) = Cache::Memcached -> new
({
        namespace => 'data.session.id',
        servers   => ['127.0.0.1:11211'],
});
my($test) = $memd -> set(time => time);
if (! $test || ($test != 1) )
{
        print "memcached is not responding. \n";
        exit;
}
$memd -> delete('time');
my($type)    = 'driver:Memcached;id:SHA1;serialize:DataDumper'; # Case-sensitive.
my($session) = Data::Session -> new
(
        cache => $memd,
        type  => $type,
) || die $Data::Session::errstr;
  1. Using a file to hold the ids. See scripts/file.autoincrement.pl:
# The EXLOCK is for BSD-based systems.
my($directory) = File::Temp::newdir('temp.XXXX', CLEANUP => 1, EXLOCK => 0, TMPDIR => 1);
my($file_name) = 'autoinc.session.dat';
my($id_file)   = File::Spec -> catfile($directory, $file_name);
my($type)      = 'driver:File;id:AutoIncrement;serialize:DataDumper'; # Case-sensitive.
my($session)   = Data::Session -> new
(
        id_base     => 99,
        id_file     => $id_file,
        id_step     => 2,
        type        => $type,
) || die $Data::Session::errstr;
  1. Using a file to hold the ids. See scripts/file.sha1.pl (non-CGI context):
my($directory) = '/tmp';
my($file_name) = 'session.%s.dat';
my($type)      = 'driver:File;id:SHA1;serialize:DataDumper'; # Case-sensitive.

# Create the session:
my($session)   = Data::Session -> new
(
        directory => $directory,
        file_name => $file_name,
        type      => $type,
) || die $Data::Session::errstr;

# Time passes...

# Retrieve the session:
my($id)      = $session -> id;
my($session) = Data::Session -> new
(
        directory => $directory,
        file_name => $file_name,
        id        => $id, # <== Look! You must supply the id for retrieval.
        type      => $type,
) || die $Data::Session::errstr;
  1. As a variation on the above, see scripts/cgi.sha1.pl (CGI context but command line program):
# As above (scripts/file.sha1.pl), for creating the session. Then...

# Retrieve the session:
my($q)       = CGI -> new; # CGI form data provides the id.
my($session) = Data::Session -> new
(
        directory => $directory,
        file_name => $file_name,
        query     => $q, # <== Look! You must supply the id for retrieval.
        type      => $type,
) || die $Data::Session::errstr;

Also, much can be gleaned from t/basic.t and t/Test.pm. See "Test Code".


๐Ÿฆ‹ Description ๐Ÿฆ‹

Data::Session is typically used by a CGI script to preserve state data between runs of the script. This gives the end user the illusion that the script never exits. ๐Ÿ’ซ

It can also be used to communicate between 2 scripts, as long as they agree beforehand what session id to use. ๐Ÿค

See Data::Session::CGISession for an extended discussion of the design changes between Data::Session and CGI::Session.

Data::Session stores user data internally in a hashref, and the module reserves key names starting with _. ๐Ÿ”‘

The current list of reserved keys is documented under "flush()".

Of course, the module also has a whole set of methods to help manage state. ๐Ÿ’…


๐Ÿ‘‘ Methods ๐Ÿ‘‘

๐Ÿ’– new() ๐Ÿ’–

Calling new() returns a object of type Data::Session, or - if new() fails - it returns undef. For details see "Trouble with Errors". ๐Ÿ˜ญ

new() takes a hash of key/value pairs, some of which might mandatory. Further, some combinations might be mandatory.

The keys are listed here in alphabetical order.

They are lower-case because they are (also) method names, meaning they can be called to set or get the value at any time.

But a warning: In some cases, setting them after this module has used the previous value, will have no effect. All such cases should be documented. โš ๏ธ

Beginners understandably confused by the quantity of options should consult the "Synopsis" for example code.

The questions of combinations of options, and which option has priority over other options, are addressed in the section, "Combinations of Options".

  • cache => $cache โ€” Specifies an object of type BerkeleyDB or Cache::Memcached to use for storage.

Only needed if you use 'type' like 'driver:BerkeleyDB ...' or 'driver:Memcached ...'.

See Data::Session::Driver::BerkeleyDB and Data::Session::Driver::Memcached.

Default: '' (the empty string).

  • data_col_name => $string โ€” Specifies the name of the column holding the session data, in the session table.

This key is optional.

Default: 'a_session'.

  • data_source => $string โ€” Specifies a value to use as the 1st parameter in the call to DBI's connect() method.

A typical value would be 'dbi:Pg:dbname=project'.

This key is optional. It is only used if you do not supply a value for the 'dbh' key.

Default: '' (the empty string).

  • data_source_attrs => $hashref โ€” Specify a hashref of options to use as the last parameter in the call to DBI's connect() method.

This key is optional. It is only used if you do not supply a value for the 'dbh' key.

Default: {AutoCommit => 1, PrintError => 0, RaiseError => 1}.

  • dbh => $dbh โ€” Specifies a database handle to use to access the session table.

This key is optional.

However, if not specified, you must specify a value for 'data_source', and perhaps also 'username' and 'password', so that this module can create a database handle.

If this module does create a database handle, it will also destroy it, whereas if you supply a database handle, you are responsible for destroying it.

  • debug => $Boolean โ€” Specifies that debugging should be turned on (1) or off (0) in Data::Session::File::Driver and Data::Session::ID::AutoIncrement.
  • debug => $integer โ€” when it's 1, $! gets tucked into error messages too. But bestie, that leaks directory names ๐Ÿ˜ญ, which is exactly why it's 0 by default.

Optional key.

Default: 0.

  • directory => $string โ€” tells the module which directory to stash session files in, for when every session lives in its own separate file (i.e. when the first component of type is 'driver:File ...').

Optional key.

Default: your temp directory, as worked out by File::Spec.

Peep "Specifying Session Options" for the whole story.

  • file_name => $string_containing_%s โ€” the naming pattern for those session files, again for when each session is stored in its own file ('driver:File ...' as the first component of type).

Optional key.

Default: 'cgisess_%s', and that %s gets swapped out at run-time for the actual session id. โœจ

Which directory they land in is decided by the directory option right above.

See "Specifying Session Options" for details.

  • host => $string โ€” a host, usually for when your data_source is pointing at MySQL.

Optional key.

Default: '' (the empty string).

  • id => $string โ€” an id to go fetch from storage.

Optional key.

Default: 0.

Note: if you don't hand over an id here, the module calls user_id() to figure out whether an id is even available from a cookie or a form field.

This whole complicated saga is in the section "Specifying an Id".

  • id_col_name => $string โ€” the name of the column in the session table that holds the session id.

Optional key.

Default: 'id'.

  • id_base => $integer โ€” the base you start counting ids from when you're using the '... id:AutoIncrement ...' component inside type.

Note: the very first id Data::Session::ID::AutoIncrement hands back is id_base + id_step. So if id_base is 1000 and id_step is 10, the lowest id you'll ever see is 1010. No cap. ๐Ÿงฎ

Optional key.

Default: 0.

  • id_file => $file_path_and_name โ€” the file path and name where the last used id gets stored, as calculated from id_base + id_step, when you're using the '... id:AutoIncrement ...' component in type.

This value has to contain a path, because the directory option above is only used for session files (when you're running Data::Session::Driver::File).

Optional key.

Default: File::Spec -> catdir(File::Spec -> tmpdir, 'data.session.id').

  • id_step => $integer โ€” how big the jump is between ids when you're using the '... id:AutoIncrement ...' component of type.

Optional key.

Default: 1.

  • name => $string โ€” the name of the cookie or form field that holds the session id.

Optional key.

Default: 'CGISESSID'.

How to actually use name is discussed in the sections "Specifying an Id" and "user_id()".

  • no_flock => $boolean โ€” no_flock => 1 means don't use flock() to grab a lock on a session file before processing it; no_flock => 0 means do use flock().

Optional key.

Default: 0.

This value is used in these cases:

  • type => 'driver:File ...'
  • type => '... id:AutoIncrement ...'
  • no_follow => $boolean โ€” influences the mode used when calling sysopen() on session files.

"Influences" here means the value is bit-wise OR'd with O_RDWR for reading and with O_WRONLY for writing.

Optional key.

Default: eval { O_NOFOLLOW } || 0.

This value is used in this case:

  • type => 'driver:File ...'
  • password => $string โ€” a value to use as the 3rd parameter when calling DBI's connect() method.

Optional key. It only gets used if you don't supply a value for the dbh key.

Default: '' (the empty string).

  • pg_bytea => $boolean โ€” says you're using the Postgres-specific column type 'bytea' to hold the session data in the session table.

Optional key, but go read "Combinations of Options" for how it interacts with the pg_text key.

Default: 0.

Warning: columns of type bytea can hold null characters (\x00), whereas columns of type text cannot. ๐Ÿ’€
  • pg_text => $boolean โ€” says you're using the Postgres-specific column type 'text' to hold the session data in the session table.

Optional key, but see "Combinations of Options" for how it interacts with the pg_bytea key.

Default: 0.

Warning: columns of type bytea can hold null characters (\x00), whereas columns of type text cannot. ๐Ÿ’€
  • port => $string โ€” a port, typically for when your data_source is pointing at MySQL.

Optional key.

Default: '' (the empty string).

  • query => $q โ€” the query object.

If you don't specify it, the next option โ€” query_class โ€” gets used to create a query object instead.

Either way, you can reach the object through the $session -> query() method.

Optional key.

Default: '' (the empty string).

  • query_class => $class_name โ€” the class of query object to create, if no value was given for the query option.

Optional key.

Default: 'CGI'.

  • socket => $string โ€” a socket, typically for when your data_source is pointing at MySQL.

The reason this key is called socket and not mysql_socket is in case other drivers ever allow a socket option.

Optional key.

Default: '' (the empty string).

  • table_name => $string โ€” the name of the table holding the session data.

Optional key.

Default: 'sessions'.

  • type => $string โ€” the type of Data::Session object you wanna create. ๐Ÿ‘‘

Optional key.

Default: 'driver:File;id:MD5;serialize:DataDumper'.

This complex topic gets its own section, "Specifying Session Options".

  • umask => $octal_number โ€” the mode to use when calling sysopen() on session files.

This value is used in these cases:

  • type => 'driver:File ...'
  • type => '... id:AutoIncrement ...'

Default: 0660 (octal).

  • username => $string โ€” a value to use as the 2nd parameter when calling DBI's connect() method.

Optional key. It's only used if you don't supply a value for the dbh key.

Default: '' (the empty string).

  • verbose => $integer โ€” print more or less information to STDERR.

Typical values are 0, 1 and 2.

Optional key.

Default: 0, meaning nothing gets printed.

See "dump([$heading])" for what goes down when verbose is 2.


๐ŸŽ€ Specifying Session Options ๐ŸŽ€

See also "Case-sensitive Options".

The default type string is 'driver:File;id:MD5;serialize:DataDumper'. It's made of 3 optional components, separated by semi-colons.

Each of those 3 components is made of 2 fields (a key and a value), separated by a colon.

The keys:

  • driver โ€” this specifies what type of persistent storage you want for your session data.

Values for driver:

  • BerkeleyDB โ€” use BerkeleyDB for storage. In this case you must pass an object of type BerkeleyDB to new() as the value of the cache option.

See Data::Session::Driver::BerkeleyDB.

  • File โ€” the default, 'File', says sessions are each stored in a separate file.

The directory for these files is specified with the directory option to new().

If no directory is given that way, File::Spec is used to find your temp directory.

The names of the session files are generated from the file_name option to new().

The default file name (pattern) is 'cgisess_%s', where the %s is replaced by the session id.

See Data::Session::Driver::File.

  • Memcached โ€” use "memcached" for storage. In this case you must pass an object of type Cache::Memcached to new() as the value of the cache option.

See Data::Session::Driver::Memcached.

  • mysql โ€” this says each session is stored in a separate row of a database table, using the DBD::mysql database server.

These rows have a unique primary id equal to the session id.

See Data::Session::Driver::mysql.

  • ODBC โ€” this says each session is stored in a separate row of a database table, using the DBD::ODBC database connector.

These rows have a unique primary id equal to the session id.

See Data::Session::Driver::ODBC.

  • Oracle โ€” this says each session is stored in a separate row of a database table, using the DBD::Oracle database server.

These rows have a unique primary id equal to the session id.

See Data::Session::Driver::Oracle.

  • Pg โ€” this says each session is stored in a separate row of a database table, using the DBD::Pg database server.

These rows have a unique primary id equal to the session id.

See Data::Session::Driver::Pg.

  • SQLite โ€” this says each session is stored in a separate row of a database table, using the SQLite database server.

These rows have a unique primary id equal to the session id.

The advantage of SQLite is that a client and a server are shipped with all recent versions of Perl. Bestie, it's built in. ๐Ÿซถ

See Data::Session::Driver::SQLite.

  • id โ€” this specifies what type of id generator you want to use.

Values for id:

  • AutoIncrement โ€” this says ids are generated starting from a value specified with the id_base option to new(), and the last-used id is stored in the file name given by the id_file option to new().

This file name must include a path, since the directory option to new() is not used here.

When a new id is needed, the value in the file is incremented by the value of the id_step option to new(), and the new value is both written back to the file and returned as the new session id. Two birds, one stone. ๐Ÿฆ‹

The default value of id_base is 0, and the default value of id_step is 1. Together, that means the first id available as a session id is id_base + id_step = 1.

  • The sequence starts when the module cannot find the given file, or when its contents are not numeric.

See Data::Session::ID::AutoIncrement.

  • MD5 โ€” the default, bestie. 'MD5' says ids are generated by Digest::MD5.

See Data::Session::ID::MD5.

  • SHA1 โ€” says ids get generated by Digest::SHA, using a digest algorithm of 1. ๐Ÿงš

See Data::Session::ID::SHA1.

  • SHA256 โ€” same vibe, Digest::SHA, digest algorithm of 256. โœจ

See Data::Session::ID::SHA256.

  • SHA512 โ€” you guessed it, Digest::SHA, digest algorithm of 512. ๐Ÿ’œ

See Data::Session::ID::SHA512.

  • Static โ€” this one says the id you passed into new() as the value of the 'id' option gets used as the session id for every session. ๐Ÿ‘‘

Obviously that id has to have a true value, bestie. Data::Session dies on all values Perl regards as false. No cap.

See Data::Session::ID::Static.

  • UUID16 โ€” says ids get generated by Data::UUID, producing a 16 byte long binary UUID.

See Data::Session::ID::UUID16.

  • UUID34 โ€” Data::UUID again, this time a 34 byte long string UUID. ๐ŸŒธ

See Data::Session::ID::UUID34.

  • UUID36 โ€” Data::UUID, 36 byte long string UUID. ๐Ÿฆ‹

See Data::Session::ID::UUID36.

  • UUID64 โ€” Data::UUID, generating a 24 (sic) byte long, base-64 encoded, UUID. Yes, the (sic) is staying, that's the original's energy. ๐Ÿ’…

See Data::Session::ID::UUID64.

Also go peek at scripts/digest.pl, which prints the length of each type of digest. ๐Ÿ“

  • serialize โ€” this one says what kind of mechanism you wanna use to turn the in-memory session data into a form that suits your chosen storage type.

Values for 'serialize':

  • DataDumper โ€” use Data::Dumper to freeze/thaw sessions.

See Data::Session::Serialize::DataDumper.

  • FreezeThaw โ€” use FreezeThaw to freeze/thaw sessions.

See Data::Session::Serialize::FreezeThaw.

  • JSON โ€” use JSON to freeze/thaw sessions.

See Data::Session::Serialize::JSON.

  • Storable โ€” use Storable to freeze/thaw sessions.

See Data::Session::Serialize::Storable.

Warning: Storable should be avoided until this problem is fixed: <http://rt.cpan.org/Public/Bug/Display.html?id=36087>. Fr fr, stay away from this one for now. ๐Ÿ˜ญ
  • YAML โ€” use YAML::Tiny to freeze/thaw sessions.

See Data::Session::Serialize::YAML.


โœจ Case-sensitive Options โœจ

Just to emphasize, bestie: the names of drivers, etc follow the DBD::* (or similar) style of case-sensitivity. So like, mind your capitalisation. ๐Ÿ’…

The following classes for drivers, id generators and serializers are shipped with this package. ๐ŸŽ€

Drivers:

  • Data::Session::Driver::BerkeleyDB
    • This name comes from BerkeleyDB.
    • And yes, the module uses BerkeleyDB and not DB_File.
  • Data::Session::Driver::File
  • Data::Session::Driver::Memcached
    • This name comes from Cache::Memcached even though the external program you run is called memcached. Yes, that's the whole drama. ๐Ÿ™ˆ
  • Data::Session::Driver::mysql
  • Data::Session::Driver::ODBC
  • Data::Session::Driver::Oracle
  • Data::Session::Driver::Pg
  • Data::Session::Driver::SQLite

ID generators:

  • Data::Session::ID::AutoIncrement
  • Data::Session::ID::MD5
  • Data::Session::ID::SHA1
  • Data::Session::ID::SHA256
  • Data::Session::ID::SHA512
  • Data::Session::ID::Static
  • Data::Session::ID::UUID16
  • Data::Session::ID::UUID34
  • Data::Session::ID::UUID36
  • Data::Session::ID::UUID64

Serializers:

  • Data::Session::Serialize::DataDumper
  • Data::Session::Serialize::FreezeThaw
  • Data::Session::Serialize::JSON
  • Data::Session::Serialize::Storable
Warning: Storable should be avoided until this problem is fixed: <http://rt.cpan.org/Public/Bug/Display.html?id=36087>. ๐Ÿ‘€
  • Data::Session::Serialize::YAML

๐ŸŒˆ Specifying an Id ๐ŸŒˆ

user_id() gets called to figure out if an id is available from a cookie or a form field. ๐Ÿช

There are several cases to consider:

  • You specify an id which exists in storage โ€” you can check this with the call $session -> is_new, which will return 0.

$session -> id will return the old id.

  • You do not specify an id โ€” the module generates a new session and a new id.

You can check this with the call $session -> is_new, which will return 1.

$session -> id will return the new id.

  • You specify an id which does not exist in storage โ€” you can check this with the call $session -> is_new, which will return 1.

$session -> id will return the old id.

So how do you tell the difference between the last 2 cases, bestie? Like this: ๐Ÿ’–

if ($session -> id == $session -> user_id)
{
    # New session using user-supplied id.
}
else
{
    # New session with new id.
}

๐Ÿ’œ Combinations of Options ๐Ÿ’œ

See also "Specifying Session Options", for options-related combinations. (iykyk ๐Ÿ‘€)

  • dbh โ€” if you don't specify a value for the 'dbh' key, this module must create a database handle in those cases when you specify a database driver of some sort in the value for 'type'.

To create that handle, we need a value for 'data_source', and that in turn may require values for 'username' and 'password'.

When using SQLite, just specify a value for 'data_source'. The default values for 'username' and 'password' โ€” empty strings โ€” will work. Slay. โœจ

  • file_name and id_file โ€” when using new(type => 'driver:File;id:AutoIncrement;...'), then file_name is ignored and id_file is used.

If id_file is not supplied, it defaults to File::Spec -> catdir(File::Spec -> tmpdir, 'data.session.id').

When using new(type => 'driver:File;id:<Not AutoIncrement>;...'), then id_file is ignored and file_name is used.

If file_name is not supplied, it defaults to 'cgisess_%s'. Note the mandatory %s. Don't skip it, bestie, it's load-bearing. ๐Ÿซถ

  • pg_bytea and pg_text โ€” if you set 'pg_bytea' to 1, then 'pg_text' will be set to 0.

If you set 'pg_text' to 1, then 'pg_bytea' will be set to 0.

If you set them both to 0 (i.e. the default), then 'pg_bytea' will be set to 1.

If you set them both to 1, 'pg_bytea' will be left as 1 and 'pg_text' will be set to 0.

This choice was made because you really should be using a column type of 'bytea' for a_session in the sessions table, since the type 'text' does not handle null (\x00) characters. Periodt. ๐Ÿ’…


๐ŸŒŸ atime([$atime]) ๐ŸŒŸ

The [] means it's an optional parameter, bestie.

Returns the last access time of the session. ๐Ÿ•

By default, the value comes from calling Perl's time() function, or you may pass in a time, which is then used to set the last access time of the session.

This latter alternative is used by "load_session()".

See also "ctime()", "etime()" and "ptime()".

๐ŸŒŸ check_expiry() ๐ŸŒŸ

Checks that there's an expiry time set for the session, and, if (atime + etime) < time():

  • Deletes the session
    • See "delete()" for precisely what this means.
  • Sets the expired flag
    • See "expired()".

This gets used when the session is loaded, when you call "http_header([@arg])", and by scripts/expire.pl. ๐Ÿซง

๐ŸŒŸ clear([$name]) ๐ŸŒŸ

The [] means it's an optional parameter.

Returns 1. โœจ

Specifies that you wish to delete parameters stored in the session, i.e. stored by previous calls to param().

$name is a parameter name or an arrayref of parameter names.

If $name is not specified, it is set to the list of all unreserved keys (parameter names) in the session.

See "param([@arg])" for details.

The [] means it's an optional parameter.

Returns a cookie, or '' (the empty string) if the query object does not have a cookie() method. ๐Ÿช

Use the @arg parameter to pass any extra parameters to the query object's cookie() method.

Warning: Parameters which are handled by Data::Session, and hence should not be passed in, are: - -expires - -name - -value

See "http_header([@arg])" and scripts/cookie.pl.

๐ŸŒŸ ctime() ๐ŸŒŸ

Returns the creation time of the session. ๐ŸŒฑ

The value comes from calling Perl's time() function when the session was created.

This is not the creation time of the session object, except for new sessions. (Important distinction, bestie.)

See also "atime()", "etime()" and "ptime()".

๐ŸŒŸ delete() ๐ŸŒŸ

Returns the result of calling the driver's remove() method.

Specifies that you want to delete the session. Here's what it does:

  • Immediately deletes the session from storage ๐Ÿ’€
  • Calls clear()
    • This deletes all non-reserved parameters from the session object, and marks it as modified.
  • Marks the session object as deleted

That last step means that when (or if) the session object goes out of scope, it will not be flushed to storage.

Likewise, if you call flush(), the call will be ignored.

Nevertheless, the session object is still fully functional โ€” it just can't be saved or retrieved. Main character with no exit. ๐Ÿ‘‘

See also "deleted()" and "expire([@arg])".

๐ŸŒŸ deleted() ๐ŸŒŸ

Returns a Boolean (0/1) indicating whether or not the session has been deleted.

See also "delete()" and "expire([@arg])".

๐ŸŒŸ dump([$heading]) ๐ŸŒŸ

The [] means it's an optional parameter.

Dumps the session's contents to STDERR, with a prefix of '# '. ๐Ÿ“

The $heading, if any, is written first, on a line by itself, with the same prefix.

This is especially useful for testing, since it fits in with the Test::More method diag().

When verbose is 2, dump is called at these times:

  • When a session is flushed
  • As soon as a session is loaded
  • As soon as expiry is checked on a just-loaded session
  • As soon as parameter expiry is checked on a just-loaded session

๐ŸŒŸ etime() ๐ŸŒŸ

Returns the expiry time of the session. โณ

This is the same as calling $session -> expiry(). In fact, this just calls $session -> etime.

See also "atime()", "ctime()" and "ptime()".

๐ŸŒŸ expire([@arg]) ๐ŸŒŸ

The [] means it's an optional parameter.

Specifies that you wish to set or retrieve the session's expiry time, or set the expiry times of session parameters.

Integer time values ($time below) are assumed to be seconds. The value may be positive or 0 or negative.

These expiry times are relative to the session's last access time, not the session's creation time. Don't mix those up, bestie. ๐Ÿ’…

In all cases, a time of 0 disables expiry.

This affects users of Cache::Memcached. See below and Data::Session::Driver::Memcached.

When a session expires, it is deleted from storage. See delete() for the deets.

The vibe check for whether a session has expired only happens when a session is loaded from storage. That's it. That's the whole test. ๐Ÿซถ

When a session parameter expires, it gets deleted from the session object. See clear([$name]) for details.

Same energy: the test for whether a session parameter has expired only takes place when a session is loaded from storage. No sneaky background timers, bestie.

  • $session -> expire() โ€” use this to get back the session's expiry time. It literally just calls $session -> etime.

The default expiry time is 0, meaning the session will never expire. Likewise, by default, session parameters never expire. โœจ

  • $session -> expire($time) โ€” use this to set the session's expiry time.

Use these suffixes to change how the integer you hand over gets interpreted:

  • s โ€” Second
  • m โ€” Minute
  • h โ€” Hour
  • d โ€” Day
  • w โ€” Week
  • M โ€” Month
  • y โ€” Year

So $session -> expire('2h') means "expire the session in 2 hours", periodt. โฐ

expire($time) calls validate_time($time) to do the conversion from '2h' into seconds, so validate_time($time) is available for you to use too. iykyk.

If you're setting a time like this, expire($time) returns 1.

Note: the time you set here gets passed as the 3rd parameter to the storage driver's store() method (for all types of storage), and from there as the 3rd parameter to the set() method of Cache::Memcached. And of course, this doesn't happen immediately โ€” it only happens when the session is saved. ๐Ÿ™ˆ
  • $session -> expire($key_1 => $time_1[, $key_2 => $time_2...]) โ€” use this to set the expiry times of session parameters. ๐Ÿ’…

Special cases (the dramatic ones):

  • To expire the session immediately, call delete()
  • To expire a session parameter immediately, call clear($key)

See also atime(), ctime(), etime(), delete() and deleted().


expired()

Returns a Boolean (0/1) telling you whether or not the session has expired. ๐ŸŒธ

See delete().


flush()

Returns 1.

Specifies that you want the session object immediately written to storage.

If you have previously called delete(), the call to flush() is ignored. ๐Ÿšซ

If the object has not been modified, the call to flush() is ignored too.

Warning: With persistent environments, you object may never go out of scope that way you think it does. See Trouble with Exiting for details. ๐Ÿ’€

These reserved session parameters are included in what's written to storage:

  • _SESSION_ATIME โ€” the session's last access time.
  • _SESSION_CTIME โ€” the session's creation time.
  • _SESSION_ETIME โ€” the session's expiry time.

A time of 0 means there is no expiry time.

This affect users of Cache::Memcached. See expire([@arg]) and Data::Session::Driver::Memcached.

  • _SESSION_ID โ€” the session's id.
  • _SESSION_PTIME โ€” a hashref of session parameter expiry times.

http_header([@arg])

The [] means an optional parameter. โœจ

Returns a HTTP header. And notice how I said returns โ€” it does not print the header. You have to do that yourself, when appropriate. ๐Ÿ‘‘

Unlike CGI::Session, Data::Session does not force the document type to be 'text/html'.

You must pass in a document type to http_header(), like $session -> http_header('-type' => 'text/html'), or just use the query object's default.

Both CGI and CGI::Simple default to 'text/html'.

Data::Session handles the case where the query object doesn't have a cookie() method by calling $session -> cookie() to generate either a cookie, or '' (the empty string). ๐Ÿ“

The @arg parameter, if there is one, is passed to the query object's header() method, after the cookie parameter, if any.


id()

Returns the id of the session. That's it. That's the function. ๐ŸŽ€


is_new()

Returns a Boolean (0/1).

Specifies you want to know if the session object was created from scratch (1) or was retrieved from storage (0). ๐ŸŒˆ


load_param([$q][, $name])

The [] means optional parameters.

Returns $q.

Loads (copies) all non-reserved parameters from the session object into the query object. ๐Ÿฆ‹

save_param([$q][, $name]) performs the opposite operation.

$q is a query object, and $name is a parameter name or an arrayref of names.

If the query object is not specified, it generates one by calling $session -> load_query_class, and stores it in the internal 'query' attribute.

If you don't provide $q, use undef โ€” don't just omit the parameter. (Yes, this matters. ๐Ÿ™ˆ)

If $name is specified, only the session parameters named in the arrayref are processed.

If $name is not specified, it copies all parameters belonging to the query object.


load_query_class()

Returns the query object.

This calls $session -> query_class -> new if the session object's query object isn't defined.


load_session()

Returns a session.

Note: this method does not take any parameters, and hence does not function in the same way as load(...) in CGI::Session. Different girl, different algorithm. ๐Ÿ’…

The algorithm, bestie:

  1. If user_id() returns a session id, try to load that session.
    • If that succeeds, return the session.
    • If it fails, generate a new session, and return it.

You can call is_new() to tell the difference between these 2 cases. ๐Ÿ”

  1. If user_id() returns 0, generate a new session, and return it.

modified()

Returns a Boolean (0/1) telling you whether or not the session's parameters have been modified.

However โ€” and this is the messy part โ€” changing a value from one form of not-defined, e.g. undef, to another form of not-defined, e.g. 0, is ignored, meaning the modified flag is not set. In such cases, you could set the flag yourself. You got this. ๐Ÿซถ

Note: Loading a session from storage changes the session's last access time, which means the session has been modified.

If you wish to stop the session being written to storage, without deleting it, you can reset the modified flag with $session -> modified(0).


param([@arg])

The [] indicates an optional parameter.

Specifies that you wish to retrieve data stored in the session, or you wish to store data in the session. ๐ŸŒธ

Data is stored in the session object as in a hash, via a set of $key => $value relationships.

Use $session -> param($key_1 => $value_1[, $key_2 => $value_2...]) to store data in the session.

If storing data, param() returns 1.

The values stored in the session may be undef.

Note: if the value being stored is the same as the pre-existing value, the value in the session is not updated, which means the last access time does not change. No update, no timestamp change. periodt.

Use $session -> param() to return a sorted list of all keys. That call returns a list of the keys you have previously stored in the session.

Use $session -> param('key') to return the value associated with the given key.

See also clear([$name]).


ptime()

Returns the hashref of session parameter expiry times. โœจ

Keys are parameter names and values are expiry times in seconds.

These expiry times are set by calling expire([@arg]).

See also atime(), ctime() and etime().


save_param([$q][, $name])

The [] indicate optional parameters.

Returns 1.

Loads (copies) all non-reserved parameters from the query object into the session object. ๐Ÿฆ‹

load_param([$q][, $name]) performs the opposite operation.

$q is a query object, and $name is a parameter name or an arrayref of names.

If the query object is not specified, it generates one by calling $session -> load_query_class, and stores it in the internal 'query' attribute. This means you can retrieve it with $session -> query. ๐ŸŽ€

If you don't provide $q, use undef โ€” don't just omit the parameter.

If $name is specified, only the session parameters named in the arrayref are processed.

If $name is not specified, it copies all parameters.


traverse($sub)

Returns 1.

Specifies that you want $sub called for each session id found in storage, with one (1) id as the only parameter in each call. ๐Ÿ”

Note: traverse($sub) does not load the sessions, and hence has no effect on the session's last access time.

See scripts/expire.pl.


user_id()

Returns either a session id, or 0.

The algorithm, once again:

  1. If $session -> id() returns a true value, return that.

E.g. The user supplied one in $session -> new(id => $id).

Return this id.

  1. Try to recover an id from the cookie object or the query object.
    • If the query object supports the cookie method, call $self -> query -> cookie and (if that doesn't find an id), $self -> query -> param.
    • If the query object does not support the cookie method, just call $self -> query -> param.
    • Return any id found, or 0.
Note: the name of the cookie, and the name of the CGI form field, is passed to new() by the 'name' option. ๐Ÿ“

validate_options()

Cross-check a few things.

E.g. When using type => '... id:Static ...', you must supply a (true) id to new(id => ...'). โœ…


validate_time($time)

Dies for an invalid time string, or returns the number of seconds corresponding to $time, which may be positive or negative. โฐ

See expire([@arg]) for details on the time string format.


๐Ÿงช Test Code ๐Ÿงช

t/basic.ini and t/bulk.ini contain DSNs for BerkeleyDB, File, Memcache, MySQL, Pg and SQLite. Actually, they're the same file, just with different DSNs activated. ๐Ÿ‘‘

So you can use t/basic.t to run minimal tests (with only File and SQLite activated) like this:

perl -Ilib t/basic.t

or you can edit t/bulk.ini as desired, and pass it in like this:

perl -Ilib t/basic.t t/bulk.ini

Simple instructions for installing BerkeleyDB (Oracle and Perl) are in Data::Session::Driver::Berkeley.

Simple instructions for installing Cache::Memcached and memcached are in Data::Session::Driver::Memcached. ๐ŸŒˆ

โœจ FAQ โœจ

๐ŸŽ€ Guidelines re Sources of Confusion ๐ŸŽ€

So this whole bit is basically the "bestie, why is my code being like this" starter pack. It's all about stuff that trips up beginners:

  • 1: Both Data::Session and CGI::Snapp have a param() method

Okay so picture it: your CGI script sub-classes CGI::Application or its successor CGI::Snapp.

Then inside your sub-class's methods, this works:

$self -> param(a_key => 'a_value');

Time passes...

my($value) = $self -> param('a_key');

and that's because those 2 modules each implement a method called param(). Basically you're stashing a value (via param) inside $self.

But when you stash an object of type Data::Session using param(), it looks like this:

$self -> param(session => Data::Session -> new(...) );

And now plot twist ๐Ÿ’€ โ€” Data::Session itself also implements a method called param(). So to store something in the session (not in $self), you must do:

$self -> param('session') -> param(a_key => 'a_value');

Time passes...

my($value) = $self -> param('session') -> param('a_key');

And yeah it should be obvious that confusion can arise here, because the 2 objects represented by $self and $self -> param('session') both have param() methods. Two queens, same name, different castles ๐Ÿ‘‘

  • 2: How exactly should a CGI script save a session?

The first example in the Synopsis shows a very simple CGI script doing the right thing by calling flush() just before it exits. Slay.

Alternately, if you sub-class CGI::Snapp, the call to flush() is best placed in your teardown() method, which is where you override teardown() in CGI::Snapp. The point here is that your teardown() is called automatically at the end of each run mode. Iconic behaviour, no notes.

This important matter is also discussed in "General Questions" below.

  • 3: Storing array and hashes

Put simply: Don't do that! ๐Ÿ™ˆ

This will fail:

$self -> param('session') -> param(my_hash => %my_hash);

Time passes...

my(%my_hash) = $self -> param('session') -> param('my_hash');

Likewise for an array instead of a hash.

But why though? Because the part param(my_hash => %my_hash) is basically assigning a list (%my_hash) to a scalar (my_hash). Hence, only 1 element of the list (the 'first' key in some unknown order) will be assigned.

So when you try to restore the hash with my(%my_hash) ..., all you'll get back is a scalar, which will generate the classic error message 'Odd number of elements in hash assignment...'.

The solution is to use arrayrefs and hashrefs:

$self -> param('session') -> param(my_hash => {%my_hash});

Time passes...

my(%my_hash) = %{$self -> param('session') -> param('my_hash')};

Likewise for an array:

$self -> param('session') -> param(my_ara => [@my_ara]);

Time passes...

my(@my_ara) = @{$self -> param('session') -> param('my_ara')};

๐ŸŒธ General Questions ๐ŸŒธ

  • My sessions are not getting written to disk!

Because you haven't stored anything in them, bestie ๐Ÿ˜ญ. You're probably thinking sessions are saved just because they exist. They are not.

Actually, sessions are only saved if they have at least 1 parameter set. The session id and access/etc times are not enough to trigger saving.

Just do something like $session -> param(ok => 1); if you want a session saved just to indicate it exists. Code like this sets the modified flag on the session, so that flush() actually does the save.

Also, see "Trouble with Exiting", below, to understand why flush() must be called explicitly in persistent environments.

  • Why don't the test scripts use Test::Database?

I decided to circumvent it by using DBIx::Admin::DSNManager and adopting the wonders of nested testing. But, since V 1.11, I've replaced that module with Config::Tiny, to reduce dependencies, and hence to make it easier to get Data::Session into Debian. Efficiency queen ๐Ÿซถ

See t/basic.t, and in particular this line: subtest $driver => sub.

  • Why didn't you use OSSP::uuid as did CGI::Session::ID::uuid?

Because when I tried to build that module (under Debian), ./configure died, saying I had set 2 incompatible options, even though I hadn't set either of them. Make it make sense ๐Ÿ’€

  • What happens when 2 processes write sessions with the same id?

The last-to-write wins, by overwriting what the first wrote. Periodt.

  • Params::Validate be adopted to validate parameters?

Not yet.


๐Ÿ”ง Troubleshooting ๐Ÿ”ง

๐Ÿ’ฅ Trouble with Errors ๐Ÿ’ฅ

When object construction fails, new() sets $Data::Session::errstr and returns undef. Which means you can use this idiom:

my($session) = Data::Session -> new(...) || process_error($Data::Session::errstr);

However, when methods detect errors they die, so after successful object construction, you can do:

use Try::Tiny;

try
{
    $session -> some_method_which_may_die;
}
catch
{
    process_error($_); # Because $_ holds the error message.
};

๐Ÿšช Trouble with Exiting ๐Ÿšช

If the session object's clean-up code is called, in DESTROY(), the session data is automatically flushed to storage (except when it's been deleted, or has not been modified).

However โ€” and listen up, this is the important bit โ€” as explained below, there can be problems with your code (i.e. not with Data::Session) such that this clean-up code is not called, or, if called, it cannot perform as expected.

The general guideline, then, is that you should explicitly call flush() on the session object before your program exits. Say it with me: flush(), flush(), flush() ๐Ÿ’…

Common traps for beginners:

  • Creating 2 CGI-like objects

If your code creates an object of type CGI or similar, but you don't pass that object into Data::Session via the 'query' parameter to new(), this module will create one for you, which can be very confusing. Two CGI objects walking into a bar, no cap.

The solution is to always create such an object yourself, and to always pass that into Data::Session.

In the case that the user of a CGI script runs your code for the first time, there will be no session id, either from a cookie or from a form field.

In such a case, Data::Session will do what you expect, which is to generate a session id. Main character energy โœจ

  • Letting your database handle go out of scope too early

When your script is exiting, and you're trying to save session data to storage via a database handle, the save will fail if the handle goes out of scope before the session data is flushed to storage.

So, don't do that.

  • Assuming your session object goes out of scope when it doesn't

In persistent environments such as Plack, FastCGI and mod_perl, your code exits as expected, but the session object does not go out of scope in the normal way.

In cases like this, it is mandatory for you to call flush() on the session object before your code exits, since persistent environments operate in such a way that the session object's clean-up code does not get called. This means that flush() is not called automatically by DESTROY() as you would expect, because DESTROY() is not being called. Rude, honestly.

  • Creating circular references anywhere in your code

In these cases, Perl's clean-up code may not run to completion, which means the session object may not have its clean-up code called at all. As above, flush() may not get called.

If you must create circular references, it's vital you debug the exit logic using a module such as Devel::Cycle before assuming the fault is with Data::Session.

  • Using signal handlers

Write your code defensively, if you wish to call the session object's flush() method when a signal might affect program exit logic.

๐Ÿ†” Trouble with IDs ๐Ÿ†”

The module uses code like if (! $self -> id), which means ids must be (Perl) true values, so undef, 0 and '' will not work.

๐Ÿงฌ Trouble with UUID16 ๐Ÿงฌ

While testing with UUID16 as the id generator, I got this message: ... invalid byte sequence for encoding "UTF8" ...

That's because when I create a database (in Postgres) I use create database d_name owner d_owner encoding 'UTF8'; and UUID16 simply produces a 16 byte binary value, which is not guaranteed to be or contain a valid UTF8 character.

This also means you should never try to use 'driver:File;id:UUID16 ...', since the ids generated by this module would rarely if ever be valid as a part of a file name.

๐Ÿงฌ Trouble with UUID64 ๐Ÿงฌ

While testing with UUID64 as the id generator, I got this message: ... Session ids cannot contain \ or / ...

That's because I was using a File driver, and UUID's encoded in base 64 can contain /.

So, don't do that.


๐Ÿ”ข Version Numbers ๐Ÿ”ข

Version numbers < 1.00 represent development versions. From 1.00 up, they are production versions.

๐Ÿ“ฆ Repository ๐Ÿ“ฆ

<https://github.com/ronsavage/Data-Session.git>

๐Ÿ†˜ Support ๐Ÿ†˜

LBugs should be reported via the CPAN bug tracker at

<https://github.com/ronsavage/Data-Session/issues>

๐Ÿ’– Thanks ๐Ÿ’–

Many thanks are due to all the people who contributed to both Apache::Session and CGI::Session. Legends, all of them ๐Ÿซถ

Likewise, many thanks to the implementors of nesting testing. See Test::Simple.

๐Ÿ‘‘ Author ๐Ÿ‘‘

Data::Session was written by Ron Savage <ron@savage.net.au> in 2010.

Home page: <http://savage.net.au/index.html>.

Australian copyright (c) 2010, Ron Savage.

    All Programs of mine are 'OSI Certified Open Source Software';
    you can redistribute them and/or modify them under the terms of
    The Artistic License, a copy of which is available at:
    http://www.opensource.org/licenses/index.html

And that's a wrap, bestie โ€” this page is documented as perl v5.36.0, dated 2023-02-14, and it lives in Data::Session(3pm). Go forth, call flush(), and be the main character of your own session handling ๐Ÿ’…โœจ

๐Ÿ“ฆ where this page comes from