Tải bản đầy đủ - 0 (trang)
Chapter 19. Using MySQL-Based Web Session Management

Chapter 19. Using MySQL-Based Web Session Management

Tải bản đầy đủ - 0trang

19.1 Introduction

Many web applications interact with users over a series of requests and, as a result need to

remember information from one request to the next. A set of related requests is called a

session. Sessions are useful for activities such as performing login operations and associating

a logged-in user with subsequent requests, managing a multiple-stage online ordering

process, gathering input from a user in stages (possibly tailoring the questions asked to the

user's earlier responses), and remembering user preferences from visit to visit. Unfortunately,

HTTP is a stateless protocol, which means that web servers treat each request independently

of any other—unless you take steps to ensure otherwise.

This chapter shows how to make information persist across multiple requests, which will help

you develop applications for which one request retains memory of previous ones. The

techniques shown here are general enough that you should be able to adapt them to a variety

of state-maintaining web applications.



19.1.1 Session Management Issues

Some session management methods rely on information stored on the client. One way to

implement client-side storage is to use cookies, which are implemented as information

transmitted back and forth in special request and response headers. When a session begins,

the application generates and sends the client a cookie containing the initial information to be

stored. The client returns the cookie to the server with each subsequent request to identify

itself and to allow the application to associate the requests as belonging to the same client

session. At each stage of the session, the application uses the data in the cookie to determine

the state (or status) of the client. To modify the session state, the application sends the client

a new cookie containing updated information to replace the old cookie. This mechanism allows

data to persist across requests while still affording the application the opportunity to update

the information as necessary. Cookies are easy to use, but have some disadvantages. For

example, it's possible for the client to modify cookie contents, possibly tricking the application

into misbehaving. Other client-side session storage techniques suffer the same drawback.

The alternative to client-side storage is to maintain the state of a multiple-request session on

the server side. With this approach, information about what the client is doing is stored

somewhere on the server, such as in a file, in shared memory, or in a database. The only

information maintained on the client side is a unique identifier that the server generates and

sends to the client when the session begins. The client sends this value to the server with each

subsequent request so that the server can associate the client with the appropriate session.

Common techniques for tracking the session ID are to store it in a cookie or to encode it in

request URLs. (The latter is useful for clients who have cookies disabled.) The server can get

the ID as the cookie value or by extracting it from the URL.

Server-side session storage is more secure than storing information on the client, because the

application maintains control over the contents of the session. The only value present on the

client side is the session ID, so the client can't modify session data unless the application



permits it. It's still possible for a client to tinker with the ID and send back a different one, but

if IDs are unique and selected from a very large pool of possible values, it's extremely unlikely

that a malicious client will be able to guess the ID of another valid session.[1]

[1]



If you are concerned about other clients stealing valid session IDs by network snooping, you

should set up a secure connection, for example, by using SSL. But that is beyond the scope of this

book.

Server-side methods for managing sessions commonly store session contents in persistent

storage such as a file or a database. Database-backed storage has different characteristics

than file-based storage, such as that you eliminate the filesystem clutter that results from

having many session files, and you can use the same MySQL server to handle session traffic

for multiple web servers. If this appeals to you, the techniques shown in the chapter will help

you integrate MySQL-based session management into your applications. The chapter shows

how to implement server-side database-backed session management for three of our API

languages:[2]

[2]



Python is not included in the chapter because I have not found a standalone Python session

management module I felt was suitable for discussion here, and I didn't want to write one from

scratch. If you're writing Python applications that require session support, you might want to look

into a toolkit like Zope, WebWare, or Albatross.







For Perl, the Apache::Session module includes most of the capabilities you need for

managing sessions. It can store session information in files or in any of several

databases, including MySQL, PostgreSQL, and Oracle.







PHP includes native session support as of PHP 4. The implementation uses temporary

files by default, but is sufficiently flexible that applications can supply their own

handler routines for session storage. This makes it possible to plug in a storage

module that writes information to MySQL.







For Java-based web applications running under the Tomcat web server, Tomcat

provides session support at the server level. All you need to do is modify the server

configuration to use MySQL for session storage. Application programs need do nothing

to take advantage of this capability, so there are no changes at the application level.



Session support for these APIs are implemented using very different approaches. For Perl, the

language itself provides no session support, so a script must include a module such as

Apache::Session explicitly if it wants to implement a session. In PHP, the session manager is

built in. Scripts can use it with no special preparation, but only as long as they want to use the

default storage method, which is to save session information in files. To use an alternate

method (such as storing sessions in MySQL), an application must provide its own routines for

the session manager to use. Still another approach is used for Java applications running under

Tomcat, because Tomcat itself manages many of the details associated with session

management, including where to store session data. Individual applications need not know or

care where this information is stored.

Despite the differing implementations, session management typically involves a common set

of tasks:







Determining whether the client provided a session ID. If not, it's necessary to

generate a unique session ID and send it to the client. Some session managers figure

out how to transmit the session ID between the server and the client automatically.

PHP does this, as does Tomcat for Java programs. The Perl Apache::Session module

leaves it up to the application developer to manage ID transmission.







Storing values into the session for use by later requests and retrieving values placed

into the session by earlier requests. This involves performing whatever actions are

necessary that involve session data: incrementing a counter, validating a login

request, updating a shopping cart, and so forth.







Terminating the session when it's no longer needed. Some session managers make

provision for expiring sessions automatically after a certain period of inactivity.

Sessions may also be ended explicitly, if the request indicates that the session should

terminate (such as when the client selects a logout action). In response, the session

manager destroys the session record. it might also be necessary to tell the client to

release information. If the client sends the session identifier by means of a cookie, the

application should instruct the client to discard the cookie. Otherwise, the client may

continue to submit it after its usefulness has ended.



Another thing session managers have in common is that they impose little constraint on what

applications can store in session records. Sessions usually can accommodate relatively

arbitrary data, such as scalars, arrays, or objects. To make it easy to store and retrieve

session data, session managers typically serialize session information (convert it to a coded

scalar string value) before storing it and unserialize it after retrieval. The conversion to and

from serialized strings generally is not something you must deal with when providing storage

routines. It's necessary only to make sure the storage manager has a large enough repository

in which to store the serialized strings. For backing store implemented using MySQL, this

means you use a



BLOB or TEXT column.



The rest of the chapter shows a session-based script for each API. Each script performs two

tasks. It maintains a counter value that indicates how many requests have been received

during the current session, and records a timestamp for each request. In this way, the scripts

illustrate how to store and retrieve a scalar value (the counter) and a non-scalar value (the

timestamp array). They require very little user interaction. You just reload the page to issue

the next request, which results in extremely simple code.

Session-based applications often include some way for the user to log out explicitly and

terminate the session. The example scripts implement a form of "logout," but it is based on an

implicit mechanism: sessions are given a limit of 10 requests. As you reinvoke a script, it

checks the counter to see if the limit has been reached and destroys the session data if so.

The effect is that the session values will not be present on the next request, so the script

starts a new session.

The example session scripts for Perl and PHP can be found under the apache directory of the



recipes distribution, the PHP session module is located in the lib directory, and the JSP



examples are under the tomcat directory. The SQL scripts for creating the session storage

tables are located in the tables directory. As used here, the session tables are created in the



cookbook database and accessed through the same MySQL account as that used

elsewhere in this book. If you don't want to mix session management activities with those

pertaining to the other



cookbook tables, consider setting up a separate database and



MySQL account to be used only for session data. This is true particularly for Tomcat, where

session management takes place above the application level. You might not want the Tomcat

server storing information in "your" database; if not, give the server its own database.



19.2 Using MySQL-Based Sessions in Perl Applications

19.2.1 Problem

You want to use session storage for Perl scripts.



19.2.2 Solution

The Apache::Session module provides a convenient way to use several different storage types,

including one based on MySQL.



19.2.3 Discussion

Apache::Session is an easy-to-use Perl module for maintaining state information across

multiple web requests. Despite the name, this module is not dependent on Apache and can be

used in non-web contexts, for example, to maintain persistent state across multiple

invocations of a command-line script. On the other hand, Apache::Session doesn't handle any

of the issues associated with tracking the session ID (sending it to the client in response to the

initial request and extracting it from subsequent requests). The example application shown

here uses cookies to pass the session ID, on the assumption that the client has cookies

enabled.



19.2.4 Installing Apache::Session

If you don't have Apache::Session, you can get it from the CPAN (visit http://cpan.perl.org).

Installation is straightforward, although Apache::Session does require several other modules

that you may need to get first. (When you install it, Apache::Session should tell you which

required modules you need if any are missing.) After you have everything installed, create a

table in which to store session records. The specification for the table comes from the MySQL

storage handler documentation, which you can read using this command:



% perldoc Apache::Session::Store::MySQL

The table can be placed in any database you like (we'll use

must be named



sessions and have this structure:



cookbook), but the table name



CREATE TABLE sessions

(

id

CHAR(32) NOT NULL,

a_session

BLOB,

PRIMARY KEY (id)

);



# session identifier

# session data



The



id column holds session identifiers, which are 32-character MD5 values generated by

Apache::Session. The a_session column holds session data in the form of serialized



strings. Apache::Session uses the Storable module to serialize and unserialize session data.



19.2.5 The Apache::Session Interface

To use the



sessions table in a script, include the MySQL-related session module:



use Apache::Session::MySQL;

Apache:Session represents session information using a hash. It uses Perl's



tie mechanism



to map hash operations onto the storage and retrieval methods used by the underlying

storage manager. Thus, to open a session, you should declare a hash variable and pass it to



tie. The other arguments to tie are the name of the session module, the session ID, and

information about the database to use. There are two ways to specify the database

connection. First, you can pass a reference to a hash that contains connection parameters:



my %session;

tie %session,

"Apache::Session::MySQL",

$sess_id,

{

DataSource => "DBI:mysql:host=localhost;database=cookbook",

UserName => "cbuser",

Password => "cbpass",

LockDataSource => "DBI:mysql:host=localhost;database=cookbook",

LockUserName => "cbuser",

LockPassword => "cbpass"

};

In this case, Apache::Session uses the parameters to open its own connection to MySQL,

which it closes when you close or destroy the session. Second, you can pass the handle for an

already open database connection (represented here by



my %session;

tie %session,

"Apache::Session::MySQL",

$sess_id,

{

Handle => $dbh,

LockHandle => $dbh

};



$dbh):



If you pass a handle to an open connection like this, Apache::Session leaves it open when you

close or destroy the session, on the assumption that you're using the handle for other

purposes elsewhere in the script. You should close the connection yourself when you're done

with it.

The



$sess_id argument to tie represents the session identifier. Its value should be

either undef to begin a new session, or a valid ID corresponding to an existing session

record. In the latter case, the value should match that of the id column in some existing

sessions table record.

After the session has been opened, you can access its contents. For example, after opening a

new session, you'll want to determine what its identifier is so you can send it to the client.

That value can be obtained like this:



$sess_id = $session{_session_id};

Session hash element names that begin with an underscore (such as



_session_id) are



reserved by Apache::Session for internal use. Other than that, you can use names of your

own choosing for storing session values. For example, you might maintain a scalar counter

value as follows, where the counter is initialized if the session is new, then incremented and

retrieved for display:



$session{count} = 0 if !exists ($session{count});

++$session{count};

print "counter value: $session{count}\n";



# initialize counter

# increment counter

# print value



To save a non-scalar value such as an array or a hash into the session record, store a

reference to it:



$session{my_array} = \@my_array;

$session{my_hash} = \%my_hash;

In this case, changes made to



@my_array or %my_hash before you close the session



will be reflected in the session contents. To save an independent copy of an array or hash in

the session that will not change when you modify the original, create a reference to it like this:



$session{my_array} = [ @my_array ];

$session{my_hash} = { %my_hash };

To retrieve a non-scalar value, dereference the reference stored in the session:



@my_array = @{$session{my_array}};

%my_hash = %{$session{my_hash}};

To close a session when you're done with it, pass it to



untie:



untie (%session);

When you close a session, Apache::Session saves it to the



sessions table if you've made



changes to it. This also makes the session values inaccessible, so don't close the session until

you're done accessing it.



Apache::Session notices changes to "top-level" session record

values, but might not detect a change to a member of a value stored

by reference (such as an array element). If this is a problem, you

can force Apache::Session to save a session when you close it by

assigning any top-level session element a value. The session ID is

always present in the session hash, so it provides a convenient way

to force session saving:



$session{_session_id} = $session{_session_id};

An open session may be terminated rather than closed. Doing so

removes the corresponding record from the



sessions table, so



that it can be used no longer:



tied (%session)->delete ( );



19.2.6 A Sample Application

The following script, sess_track.pl, is a complete (if short) implementation of an application

that uses a session. It uses Apache::Session to keep track of the number of requests in the

session and the time of each request, updating and displaying the information each time it is

invoked. sess_track.pl uses a cookie named



PERLSESSID to pass the session ID. This is



done with the CGI.pm cookie management interface.[3]

[3]



For information about CGI.pm cookie support, use the following command and read the section

describing the cookie( ) function:



#! /usr/bin/perl -w

# sess_track.pl - session request counting/timestamping demonstration

use

use

use

use

use



strict;

lib qw(/usr/local/apache/lib/perl);

CGI qw(:standard);

Cookbook;

Apache::Session::MySQL;



my $title = "Perl Session Tracker";

my

my

my

my



$dbh = Cookbook::connect ( );

$sess_id = cookie ("PERLSESSID");

%session;

$cookie;



# connection to MySQL

# session ID (undef if new session)

# session hash

# cookie to send to client



# open the session

tie %session, "Apache::Session::MySQL", $sess_id,

{

Handle => $dbh,

LockHandle => $dbh

};

if (!defined ($sess_id))

# this is a new session

{

# get new session ID, initialize session data, create cookie for client

$sess_id = $session{_session_id};

$session{count} = 0;

# initialize counter

$session{timestamp} = [ ];

# initialize timestamp array

$cookie = cookie (-name => "PERLSESSID", -value => $sess_id);

}

# increment counter and add current timestamp to timestamp array

++$session{count};

push (@{$session{timestamp}}, scalar (localtime (time ( ))));

# construct content of page body

my $page_body =

p ("This session has been active for $session{count} requests.")

. p ("The requests occurred at these times:")

. ul (li ($session{timestamp}));

if ($session{count} < 10)

# close (and save) session

{

untie (%session);

}

else

# destroy session after 10 invocations

{

tied (%session)->delete ( );

# reset cookie to tell browser to discard session cookie

$cookie = cookie (-name => "PERLSESSID",

-value => $sess_id,

-expires => "-1d");

# "expire yesterday"

}

$dbh->disconnect ( );

# generate the output page

print

header (-cookie => $cookie) # send cookie in headers (if it's defined)

. start_html (-title => $title, -bgcolor => "white")

. $page_body

. end_html ( );

exit (0);

Try the script by installing it in your cgi-bin directory and requesting it from your browser. To

reinvoke it, use your browser's Reload function.



sess_track.pl opens the session and increments the counter prior to generating any page

output. This is necessary because the client must be sent a cookie containing the session

name and identifier if the session is new. Any cookie sent must be part of the response

headers, so the page body cannot be printed until after the headers are sent.

The script also generates the part of the page body that uses session data but saves it in a

variable rather than writing it immediately. The reason for this is that, should the session need

to be terminated, the script resets the cookie to be one that tells the browser to discard the

one it has. This too must be determined prior to sending the headers or any page count.



19.2.7 Session Expiration

The Apache::Session module requires only the



id and a_session columns in the



sessions table, and makes no provision for timing out or expiring sessions. On the other

hand, the module doesn't restrict you from adding other columns, so you could include a



TIMESTAMP column in the table to record the time of each session's last update. For

example, you can add a TIMESTAMP column t to the sessions table using ALTER

TABLE:

ALTER TABLE sessions ADD t TIMESTAMP NOT NULL;

Then you'd be able to expire sessions by running a query periodically to sweep the table and

remove old records. The following query uses an expiration time of four hours:



DELETE FROM sessions WHERE t < DATE_SUB(NOW( ),INTERVAL 4 HOUR);

Be aware that deleting session records can cause a problem:



tie will raise an exception if



you attempt to look up a session record using a non-undef session ID for which no record

exists. This means, for example, that if a client provides an ID for a session that has been

expired, your script may die with an error. One way to handle this is to open the session

within an



eval block so that you can trap the error. If one occurs, create a new session



record:



eval

{

tie %session, "Apache::Session::MySQL", $sess_id,

{

Handle => $dbh,

LockHandle => $dbh

};

};

if ($@) # if an error occurred, old session is unavailable; create a new

one

{

$sess_id = undef;

tie %session, "Apache::Session::MySQL", $sess_id,



{

Handle => $dbh,

LockHandle => $dbh

};

}



19.3 Using MySQL-Based Storage with the PHP Session

Manager

19.3.1 Problem

You want to use session storage for PHP scripts.



19.3.2 Solution

PHP 4 includes session managment. By default, it uses temporary files for backing store, but

you can configure it to use MySQL instead.



19.3.3 Discussion

PHP 4 includes a native session manager. This section shows how to use it and how to extend

it by implementing a storage module that saves session data in MySQL.[4] If your PHP

configuration has both the



track_vars and register_globals configuration



directives enabled, session variables will exist as global variables of the same names in your

script. (track_vars is enabled automatically for PHP 4.0.3 or later; for earlier versions,

you should enable it explicitly.) If



register_globals is not enabled, you'll need to

access session variables as elements of the $HTTP_SESSION_VARS global array or the

$_SESSION superglobal array. This is less convenient than relying on

register_globals, but is also more secure. (Recipe 18.6 discusses PHP's global and

superglobal arrays and the security implications of register_globals.)

[4]



PHP 3 provides no session support. PHP 3 users who require session support may wish to look

into PHPLIB or another package that includes a session manager.



19.3.4 The PHP 4 Session Management Interface

PHP's session management capabilities are based on a small set of functions, all of which are

documented in the PHP manual. The following list describes those likely to be most useful for

day-to-day session programming:



session_start ( )



Opens a session and extracts any variables previously stored in it, making them

available in the script's global namespace. For example, a session variable named



x



becomes available as



$_SESSION["x"] or

$HTTP_SESSION_VARS["x"]. If register_globals is enabled, x

also becomes available as the global variable



$x.



session_register ( var_name)

Registers a variable in the session by setting up an association between the session

record and a variable in your script. For example, to register



$count, do this:



session_register ("count");

If you make any changes to the variable while the session remains open, the new

value will be saved to the session record when the session is closed. Observe that

variables are registered by name rather than by value or by reference:



session_register ($count);

session_register (&$count);



# incorrect

# incorrect



Several variables may be registered at once by passing an array that contains multiple

names rather than by passing a single name:



session_register (array ("count", "timestamp"));

Registering a variable implicitly starts a session, which means that if a script calls



session_register( ), it need not call session_start( ) first.

However, session_register( ) is effective only if

register_globals is enabled. To avoid reliance on

register_globals, you should call session_start( ) explicitly and

get your session variables from either the $_SESSION or the

$HTTP_SESSION_VARS array.

session_unregister ( var_name)

Unregisters a session variable so that it is not saved to the session record.



session_write_close ( )

Writes the session data and closes the session. Normally you need not call this

function; PHP saves an open session automatically when your script ends. However, it

may be useful to save and close the session explicitly if you want to modify session



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Chapter 19. Using MySQL-Based Web Session Management

Tải bản đầy đủ ngay(0 tr)

×