Sessions

A feature that almost every web application needs is the ability to store application state for a certain user between requests. HTTP treats each request as being completely unrelated to every other request. This is usually undesirable in the context of a web application because some data needs to persist between page loads. For example, it would be very inconvenient to require a user to type his/her username and password on every page to authorize the next action. Sessions are the solution to this problem. When a user first visits your application, Catalyst will supply him/her with a token to identify himself/herself for a certain amount of time. Whenever that token is supplied with a request, Catalyst will find its session data and make it accessible in your Controllers throughout the request.

To get started with sessions, you'll need to install some modules and load the appropriate session plugins into your application. The modules we'll need to install are Catalyst::Plugin::Session, Catalyst::Plugin::Session::State::Cookie, and Catalyst::Plugin::Session::Store::FastMmap or Catalyst::Plugin:: Session::Store::File.

Catalyst's session engine consists of three parts. The first is Catalyst::Plugin:: Session, which provides general session functionality. For the session plugin to work, it needs to be able to do two things—store session data somewhere and be able to get a session ID token from each user. The most common form of a token is a cookie that's stored on the user's machine. When your application sets a cookie, the browser will store it and attach it to every request. Catalyst will see this cookie and then restore the appropriate session data. To use cookies to manage sessions, load Catalyst::Plugin::Session::State::Cookie into your application.

However, if the user's have disabled cookies for some reason, then this won't work. Nearly every browser and robot on the web today support cookies, so you shouldn't have to worry about browser support. If you absolutely cannot use cookies, try Catalyst::Plugin::Session::State::URI, but be aware that it can be buggy because it works by rewriting every link on your generated pages to include the session identifier in the URL. (It can also open up security issues. If a user follows a link to an external website, the external website will be able to retrieve the session ID from the Referer header and then steal the user's session. Therefore, it's recommended that you just use cookies.)

Once you have a way to identify each user, you'll need to store the data specific to the user somewhere. The most convenient method for this is Catalyst::Plugin::Session::Store:: FastMmap. FastMmap will store session data in a memory-mapped file, which is fast and efficient but won't be shared between load-balanced machines or persist between runs of your server.

Note

FastMmap won't work on Windows, but you can use Catalyst::Plugin::Session::Store::File instead.

Storing the session data in your database will eliminate these problems, but it will be slightly slower. Most real applications won't notice the speed difference. For development, Session::Store::File and Session::Store::FastMmap are the most convenient as they require no additional configuration.

After you've decided on which Session::Store and Session::State backends to use, simply list them with the other plugins. Your AddressBook.pm should look something like this:

use Catalyst qw/
-Debug
ConfigLoader
Static::Simple
Session
Session::State::Cookie
Session::Store::FastMmap
/;

When you restart your application, the first request will result in a cookie being set. Each subsequent request with that cookie will be in the same session. You'll also have access to a few new methods in your context object. The most useful are $c->session and $c->flash. These methods both work like $c->stash, except they persist for more than one request. Data stored with $c->session will persist until the session expires (which you can check via the $c->session_expires accessor). $c->flash is a little bit more complicated. It works like the stash, but will persist between HTTP redirects (explained in detail later in this chapter). Once it's read or written to after a redirect, it will be cleared out. The most convenient way of using the flash is to set:

__PACKAGE__->config(session => {flash_to_stash => 1});

in AddressBook.pm with the rest of your configuration options. With this turned on, the flash from the last request will automatically become the stash for the current request. We'll see how this is helpful in the next section.

Adding sessions to the address book

Our address book doesn't need sessions in the traditional sense quite yet. It doesn't need to store any data between requests, because nothing is specific to a single user. Every user has the same privileges and every operation changes the data for every other user. However, we can use sessions to improve the user interface.

In the version of AddressBook from Chapter 3, Creating a Real Application we programmed our mutator operations (/person/delete, for example) to set a message key in the stash and then forward back to the list action. This made deletes seamless—the user just clicked delete and then saw the updated list of people with a message at the top saying that the person had been successfully removed. The only problem was that the URL in the browser was still at /person/delete/number instead of /person/list. If a user bookmarked that URL assuming that it would lead them to the list of people, they could accidentally delete a person or just get an error message.

Catalyst will let us do better than that. To get the user's URL bar to show the real URL, we need to redirect him (via an HTTP redirection code) to the user list page. If we did that without sessions, everything in the stash would be lost and we wouldn't be able to display a status message at the top of the page. Thanks to the flash though, we can tell Catalyst to display the message on the next page load instead of the current page. All we need to do is enable flash_to_stash as done previously, store the message in $c->flash, and issue an HTTP redirect to the user list. Here's the code that implements this for the person deletion action in the person Controller (Person.pm):

sub delete : Local {
my ($self, $c, $id) = @_;
my $person = $c->model('AddressDB::People')->find({id => $id});
$c->stash->{person} = $person;
if($person){
$c->flash->{message} = 'Deleted ' . $person->name;
$person->delete;
}
else {
$c->flash->{error} = "No person $id";
}
$c->response->redirect($c->uri_for_action('person/list'));
$c->detach();
}

The only difference between this and the old code is that we are using the flash instead of the stash, and so we redirect instead of forward at the end.

The syntax for redirection might appear a bit confusing but it is really simple. Redirection works by setting a header in the HTTP response, and then sending the browser to another page. As the browser is outside the realm of Catalyst and doesn't know the full URL, we have to convert the name of the action to a real fully-qualified URL. We then set the necessary response header (Location:) to that URL. The $c->detach() line will stop all further processing of actions and jump directly to the end action. In this case, it's not strictly necessary (as the end action will be called immediately anyway), but if you're inside some if-then statements, detach will get you out as quickly as possible. After you detach, the default end action will see that this response is a redirect and will pass that on to the browser without rendering a template.

When the browser requests the /person/list page, Catalyst will restore the session, move the flash to the stash, and then handle the request as normal. (If this isn't working for you, make sure you enabled the flash_to_stash configuration option as described.) At the end of the request, the message from the flash will be displayed on top of the page, just like before. This all will be independent, so you won't have to change the templates at all.

Forwarding will continue to work as before, but you can gradually convert from forwarding to redirection as you see it. Now your users have the best of both worlds. They can bookmark any page and go to the right place, while still receiving informative status messages where necessary.

Sessions in the database

If you plan on deploying your application across multiple servers (via a load balancer), putting sessions in the database can make your life easier. You won't need to ensure that each user gets the same server for every request—everything will work perfectly even if the user moves between servers. This means that you can also take servers down without anyone losing data.

The setup isn't quite as simple as with Session::Store::FastMmap or Session:: Store::File, but it is straightforward. The first thing to do is to create a sessions table in your database as follows:

CREATE TABLE sessions (
id CHAR(72) PRIMARY KEY,
session_data TEXT,
expires INTEGER
);

Then, add a schema file, lib/AddressBook/Schema/AddressDB/Session.pm to your DBIC schema by recreating the Model as explained in the previous chapter.

$ perl script/addressbook_create.pl model AddressDB DBIC::Schema
AddressBook::Schema::AddressDB create=static dbi:SQLite:database

You will see something like the following:

exists "/Volumes/Home/Users/solar/Projects/CatalystBook/AddressBook/script/../lib/AddressBook/Model"
exists "/Volumes/Home/Users/solar/Projects/CatalystBook/AddressBook/script/../t"
Dumping manual schema for AddressBook::Schema::AddressDB to directory /Volumes/Home/Users/solar/Projects/CatalystBook/AddressBook/script/../lib ...
Schema dump completed.
exists "/Volumes/Home/Users/solar/Projects/CatalystBook/AddressBook/script/../lib/AddressBook/Model/AddressDB.pm"

Your changes in the other schema files are still intact provided you placed all the custom configurations below the following line in the schema:

# You can replace this text with custom content, and it will be preserved on regeneration

The newly-created schema for the session will look like the following (lib/Addressbook/Schema/AddressDB/Result/Sessions.pm):

package AddressBook::Schema::AddressDB::Result::Sessions;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("InflateColumn::DateTime", "Core");
__PACKAGE__->table("sessions");
__PACKAGE__->add_columns(
"id",
{ data_type => "CHAR", default_value => undef, is_nullable => 1, size => 72 },
"session_data",
{
data_type => "TEXT",
default_value => undef,
is_nullable => 1,
size => undef,
},
"expiresinteger",
{ data_type => "", default_value => undef, is_nullable => 1, size => undef },
);
__PACKAGE__->set_primary_key("id");
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2010-04-10 14:20:53
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:wRNuyHfQA4yvyNDO6V9QEw
# You can replace this text with custom content, and it will be preserved on regeneration
1;

With the Sessions schema in place, all you have to do is use Session::Store::DBIC instead of Session::Store::FastMmap and add the following configuration option in AddressBook.pm:

__PACKAGE__->config( session => {
dbic_class => 'AddressDB::Session', flash_to_stash => 1 });

You will also have to install the plugin using CPAN and add it to the makefile (for portability) as explained in Chapter 2,Creating a Catalyst Application.

When you restart your application, session information will be stored in your DBIC model.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.185.87