Beefy Boxes and Bandwidth Generously Provided by pair Networks
Just another Perl shrine
 
PerlMonks  

Using Sessions between perl and php.

by jryan (Vicar)
on Jan 20, 2002 at 01:07 UTC ( [id://140131]=perlmeditation: print w/replies, xml ) Need Help??

A very common web technique nowadays is the use of sessions. Most programming languages have support for sessions; however, what happens when you need more than one language able to access the same session? Well, it gets messy. At my most recent place of employment, we ran into the problem where we needed php and perl sessions to communicate with each other. My co-worker (a certain Steve Stetak, an awesome programmer who unfortunately isn't a perlmonk) and I eventually came up with a solution using Apache::Session::MySQL and some hacking of php's serialization.

How do I use it?

1. What are sessions?

Sessions are a method to hold state variables between different scripts or different instances of a script. For example, when I log into Hotmail. I give the first page my username and password. These values are then stored internally by the server, and accessed everytime I go to a new page. Only when I hit the logout link are these values destroyed.

2. How do they work?

When you first start a session, the script first creates a cookie on the client browser with the session ID. The session ID is a unique 32 character string that identifies each session. You scripts then register "session variables" which are stored in a MySQL database. The variables are then accessable as long as you have the session ID.

3. What are some limitations of our system?

Due to the amount of PHP and Perl scripts on the server that would need to use sessions, we must come up with a universal way to access sessions in both languages. Unfortunately, this eliminates some of the functionality of sessions. For example, the only session variable that can be used is the PHP associative array "$session" and the Perl hash "%session"

4. How do I use sessions with PHP with our system?

To begin a session with PHP, you must first require the session handler file at the beginning of all the scripts that use sessions.
require "Sessions/mysql_session_handler.php"; // more on this later

Then, to begin the session, call the function

session_start();

This function first checks to see if there is a cookie defined with the session ID. If there is, it uses that ID to load the session variable. If there is no cookie, it creates a new, random session ID and places it in a cookie. It then creates an empty session.

If there is no session defined already, then you must register the session variable. To do this, you must first define the associative array, and then register it to the session.

$session = array(); session_register("session");

You can now use the $session variable throughout your script. All members of the $session array will be available for all the scripts using the same session.

When you are finished with the session (i.e. when the user logs out) you must call the session_destroy() function do destroy the session and remove the cookie.

5. How do I use sessions with Perl while using the system?

Unfortunately, using sessions with Perl is slightly more complicated. First, you must include the following lines at the beginning of your script:

use CGI; use Apache::Session::MySQL;

The CGI modules is used to access the cookie, because, unlike PHP, Perl sessions do not handle cookies for you, while the Apache modules are used to access the MySQL database and store the session data there.

Next, you must initialize the CGI instance and get the cookie ID.

my $q = new CGI; my $sess_id = $q->cookie(-name=>'sess_id');

Now, you can begin you session, by tieing the %session hash to the MySQL database. If $sess_id is undef then it will create a new session, with a new session ID.

tie my %session, "Apache::Session::MySQL", $sess_id, { DataSource => 'dbi:mysql:sessions', UserName => 'root', Password => '', LockDataSource => 'dbi:mysql:sessions', LockUserName => 'root', LockPassword => '' };

You can now save your session variables in the hash. Note that if you pass a session ID to the tie function that does not exist, for example, from an old cookie whose data has been deleted, the script will die. See the sample Perl script for a workaround to this problem.

Note also that the session ID is stored in $session{'_session_id'}

If you have created a new session, you must set a cookie.

my $cookie = $q->cookie(-name=>'sess_id',-value=>$session{'_session_id +'}); print $q->header(-cookie=>$cookie);

When you are done with a session (i.e. the user logs out) you should destory the session. Do this with:

tied(%session)->delete;

6. How do I use sessions with PHP and Perl?

Using the above method, your sessions should work with both PHP and Perl. One condition is that, after creating a session in PHP, you must initialize the session ID variable, using the line:

$session['_session_id'] = session_id();

7. What are some things to remember?

  1. The cookie should always be named "sess_id" PHP handles this automatically, and the name is set in php.ini as "sess_id". However, in Perl the naming must be done manually.
  2. 2. Only use a single level associative array or hash. Do not try to use multiple levels in PHP, such as $session['username'][1], or use references in Perl, as in $session{username}[2]. Only store numerical and string data in session variables.
  3. 3. Never register anything but "session" in PHP. Otherwise, the sessions will not function properly, and you will not be able to retrieve your session data.
  4. 4. If you are writing a Perl script that accesses MySQL databases, you still must use DBI; Even though Apache::Session::MySQL uses DBI, it does not include it in your script. However, you do not need to use DBI for the Apache's module's sake alone.

How does it work?

Perl and PHP each use a different method to serialize the data. Serialization allows the languages to "flatten" the data. Basically, it takes all the variables you want to store and puts the data necessary to recreate these variables into a string. Perl uses the functions Freeze and Thaw from the module Storable, while PHP uses its own, native serialization function. It is not possible to modify how the language serializes the session data. However, we can modify the way PHP stores the data and we can use Perl to emulate the PHP serialization functions.

So, no modifications have been made to the Perl side of the sessions. On the PHP side, however, we have to install custom storage handlers. The first step to doing this is to modify the etc/php.ini file, setting session.set_save_handler = user; This tells PHP that we will be using custom handlers. Unfortunately, it is not possible to use custom handlers in one script and PHP's default handlers in another. So, all PHP scripts that use sessions must use the same method.

Next, we have our custom handlers. The first is mysql_sessions_handler.php:

<? function mysql_session_open ( $save_path, $session_name ) { // Since all database editting is done in Perl scripts // these functions do nothing... return true; } function mysql_session_close ( ) { return true; } function mysql_session_read ( $id ) { return `perl /usr/share/php/Sessions/perl_get_serialized_sessi +on.pl $id`; } function mysql_session_write ( $id, $serialized ) { return `perl /usr/share/php/Sessions/perl_save_serialized_sess +ion.pl $id '$serialized'`; } function mysql_session_destroy ( $id ) { $db = mysql_connect("localhost", "root"); mysql_select_db('sessions', $db); return mysql_query("DELETE FROM sessions WHERE id='$id'"); } function mysql_session_gc ( $maxlife ) { // Currently there is no good garbage collection // Be sure to call "session_destroy()" in PHP scripts return true; } session_set_save_handler ( 'mysql_session_open', 'mysql_session_close', 'mysql_session_read', 'mysql_session_write', 'mysql_session_destroy', 'mysql_session_gc' ); ?>

These functions define how the PHP scripts open, close, read, write, and destory the session. As you can see, the open and close functions are empty. Normally, we would open and close a connection to the MySQL database. However, all of that work is done in the Perl scripts.

Let's look at the read and write functions. Notice that they both call a perl script from the command line. The read function is passed a session ID and return serialized data. The Perl script below handles the data.

#!/usr/bin/perl -w use strict; use DBI; use Serialize; # NOTE!!!! # The Serialize module is not found on CPAN # it was found with a google search; # you can download it here: # http://furt.com/code/perl/serialize/ use Storable qw(freeze thaw); my $id = $ARGV[0]; my $db = DBI->connect("DBI:mysql:sessions", "root", "" ); my $sel = $db->prepare("SELECT a_session FROM sessions WHERE id='$id'" +); $sel->execute; my @data = $sel->fetchrow_array; if ( $data[0] ) { my $ref = thaw($data[0]); print "session|" . serialize($ref); } else { print ""; } $sel->finish; $db->disconnect;

The Perl script takes a single argument, the session ID. It then opens the MySQL database and returns the data. Remember, that the data in the MySQL database will always be "frozen", meaning serialized by the Perl functions. So, next the script will thaw the data. The script now has a Perl reference to the session data. It then uses the serialize function which emulates PHP's serialization function.. Notice also, that the script prepends "session|" This is due to differences in the freezing and serialization functions. So, this script outputs the serialized session data, and gives it to the custom handler, which feeds it back to PHP.

On to the write function. The write function gets passed a session ID and serialized data to store. Since the data is already serialized, there is another Perl script to unserialize, freeze, and store the data.

#!/usr/bin/perl -w use strict; use DBI; use Serialize; use Storable qw(freeze thaw); my $id = $ARGV[0]; my $data = $ARGV[1]; if ( $data eq '' ) { exit(0); } $data =~ s/^session\|//; my $ref = unserialize($data); $data = freeze($ref); my $db = DBI->connect("DBI:mysql:sessions", "root", "" ); my $sel = $db->prepare("SELECT a_session FROM sessions WHERE id='$id'" +); $sel->execute; my @test = $sel->fetchrow_array; if ( $test[0] ) { $db->do( "UPDATE sessions SET a_session='$data' WHERE id='$id'" ); } else { $db->do( "INSERT INTO sessions VALUES ( '$id', '$data' )" ); } $sel->finish; $db->disconnect; print "1";

The first step is to remove "session|" from the beginning of the serialized data, so that it will freeze/thaw properly. The script then unserializes the data and freezes the reference, and then stores it in the MySQL database;

Sample PHP Script

<? require "Sessions/mysql_session_handler.php"; // put in /usr/share/php session_start(); if ( $action = "logout" ) { // Someone hit the logout button. Destroy the session. print "Session destroyed.<BR>"; session_destroy(); // The session associative array is still hanging around, // however, it is no longer attached to the session, // so we can just unset it. unset ($session); } else if ( $username ) { // The script was given a username, so let's create a session. $session['username'] = $username; // This line is to appease our Perl counterpart. $session['_session_id'] = session_id(); session_register('session'); } if ( $user = $session['username'] ) { // There is a session defined already. Let's say hi. print "Hello $user!<BR>"; print "<A HREF=\"test.php?action=logout\">Logout</A>"; } else { // This is the first time we accessed the script. // Print out a text box print "<FORM METHOD=GET ACTION=\"test.php\">"; print "What is your username? <INPUT TYPE=TEXT NAME=\"username\">" +; print "<INPUT TYPE=SUBMIT></FORM>"; } ?>

Sample Perl Script

!/usr/bin/perl -w use strict; use CGI; use Apache::Session::MySQL; use CGI::Carp qw(fatalsToBrowser); my $q = new CGI; my $sess_id = $q->cookie(-name=>'sess_id'); # substitute the name of y +our session cookie here # These are the parameters for the session my $params = { DataSource => 'dbi:mysql:sessions', UserName => 'root', Password => '', LockDataSource => 'dbi:mysql:sessions', LockUserName => 'root', LockPassword => '' }; my %session; # The following lines tie %session to the session data # The script will die if we give it a $sess_id that doesn't exist. # So we put the tie call in an eval block. If there's an error # in $@ then we create a new session. eval { tie (%session, 'Apache::Session::MySQL', $sess_id, $params); }; tie (%session, 'Apache::Session::MySQL', undef, $params) if ( $@ ); if ( $q->param('action') ) { # We should delete the session. tied(%session)->delete; # Even though the session is deleted, the hash is still hangin +g around. # So we will just undef it, so that it doesn't confuse the scr +ipt. undef %session; print $q->header; } elsif ( my $user = $q->param('username') ) { # We just created a new session. $session{'username'} = $user; # Remember to create the cookie. my $cookie = $q->cookie(-name=>'sess_id', -value=>$session{_session_id}); print $q->header(-cookie=>$cookie); } else { print $q->header; } if ( $session{'username'} ) { # The session already exists. Say hello. print "Hello $session{'username'}!<BR>"; print "<A HREF=\"test.cgi?action=logout\">Logout</A>"; } else { # Print login box print "<FORM METHOD=GET ACTION=\"test.cgi\">"; print "What is your username? <INPUT TYPE=TEXT NAME=\"username +\">"; print "<INPUT TYPE=SUBMIT></FORM>"; }

Replies are listed 'Best First'.
Re: Using Sessions between perl and php.
by dmmiller2k (Chaplain) on Jan 20, 2002 at 06:45 UTC

    A very thought-provoking treatise indeed.

    Whether or not you use Perl with PHP, this is very instructive about the workings of sesssions.

    dmm

Re: Using Sessions between perl and php.
by gav^ (Curate) on Jan 20, 2002 at 10:25 UTC

    This does seem a slightly inefficient way to do things. The PHP code has to exec a perl script to set/retrieve a session value (or did I miss something?).

    Wouldn't storing each variable in the table as a seperate row (session_id, var_name, value) work?

    gav^

      You did miss something: a very large part of the whole process. The php script has to call the perl script to de-serialize the Storable'd data (raw Storable'ized data isn't exactly the nicest to work with) and return it in a php-style serialized form that the php side can work with.

        I was thinking of having a table that goes like:

        session_id var_name value ---------------------------------------- asdijhufio4 user_id 1 asdijhufio4 state 55 fddffff0012 new_user 1 dfdfgeret44 username 'bob'
        And then it is just a matter of doing a 'SELECT var_name, value FROM session where session_id = ?'

        I would think the performance of this would be a lot better than the way you serialize your data.

        Update:It is possible to store the hash in a MySQL blob using \0 as a seperator. This may work better if you don't want to have multiple rows per session.

        Hope this helps...

        gav^

        I think gav^ is saying that just that should be avoidable. And I agree; rather than freezing/thawing a hash, one could, I think, choose to store a hash as a series of rows containing a key name and the value, each. Unless I'm still overlooking something important - but I can't seem to find anything such.

        Makeshifts last the longest.

Re: Using Sessions between perl and php.
by jryan (Vicar) on Jan 21, 2002 at 01:12 UTC

    I think the problem you guys are failing to understand is that the Apache::Session modules use Storable to serialize the data structures, and stores the data in a very precise way. It has nothing to do with me. Apache::Session is very complex, and handles many issues that I would most likely botch if I tried to roll my own Session handling module. Hence, I didn't. Besides, even if I could force the perl side to act like I want, I would still have to worry about the PHP side. PHP has a very bizarre method of serialization - and I can't change that, it is done internally. I had to cater to one or the other, and I chose to cater to perl.

    Update:  This is from Steve, my partner, perhaps it will clear some things up:

    Ok, here's the deal. First note that Apache::Session handles most of the saving sessions itself. Admittedly, we did not have to use Apache::Session, but that would mean we would have to write our own Perl session handler, and documentation for it (since jryan and I would not be the primary programmers using the system.) Also note that there were time constraints and no time to write our own complete session handling system and document it. So, the goal was to get **Apache::Session** to work with the PHP sessions. I admit that this isn't an optimal solution, but it works.

    Now, Apache::Session takes the session data, serializes it with Storable and then stores it in a MySQL database that has an exact structure that MUST be followed. As far as I could tell, there was no way to modify the way this happened, and if there was, it would not be as easy as modifying the way PHP stores the data.

    PHP, on the other hand, provides and easy method to change the way the data is stored. With PHP, it would be possible to have a MySQL table with columns for each variable, and if we were just using PHP, that is what we would have used.

    I hope this clears some things up,
    Steve

      Ok, I'll have to try and explain things better. I know how Apache::Session works and I know that it uses Storable to serialize data types to store in the database.

      What I am trying to say, have you thought that using Apache::Session is maybe the best possible solution to your problem?

      Looking at your documentation (and pointed out by Screamer) you can't use arbitrary data structures. I'm not sure where this limitation comes from but it means that you don't need the flexibility gained by using Storable.

      From your PHP code, it seems that you have to exec a perl script every time you read or write a variable. This seems very inefficient to me.

      I don't think it would be to hard, I had a quick look at the source for Apache::Session::MySQL and you can probably work from this and it's subclasses (reusing the one for locking etc) to create an Apache::Session module that doesn't use Storable.

      gav^

        You are completely missing the whole point of this: IT IS NOT PURE PERL. Its not. Really.

        We used Apache::Session because it worked. It works well. PHP sessions also worked well for us. These two systems were already in place.

        Now, we needed them to talk to each other. PHP provides a method to the default session handling functions. Rather than screw with Apache::Session::MySQL (of which we already had a bitch of a time getting installed), we decided the best route to take was to take advantage of PHP's overridable functions. So we did. The key part here (which you seem to disagree with, probably because you haven't researched this very deeply), is that we needed a way to take the php-serialized data and convert it to perl-serialized data.

        Thats where the perl script that the php script calls comes in. A php-serialized associative array is passed as a command line argument. A perl module deserializes this structure into a hash. The hash is then serialized with Storable. This string is then updated into the sessions database. A reverse process is done when the session data is extracted. Perl, on the other hand, simply grabs the data using the normal Apache::Session::MySQL.

        Yes, its a bit odd, but then it isn't a very normal task. Using our method, pure perl scripts can still take advantage of the full power of Storable, AND we didn't have to have a hacked version of Apache::Session::MySQL in order to get it to work. I fail to see how your method is any easier ours...

Re: Using Sessions between perl and php.
by BrentDax (Hermit) on Jan 21, 2002 at 12:15 UTC
    My best suggestion is to encapsulate. You shouldn't have to put code like:
    my $params = { DataSource => 'dbi:mysql:sessions', UserName => 'root', Password => '', LockDataSource => 'dbi:mysql:sessions', LockUserName => 'root', LockPassword => '' }; my %session; eval { tie (%session, 'Apache::Session::MySQL', $sess_id, $params); }; tie (%session, 'Apache::Session::MySQL', undef, $params) if ( $@ ); #etcetera
    That should all be shoved into a MySessions module and replaced with:
    use CGI; use MySessions; my $q=new CGI; MySessions->connect(); my $session=MySessions->retrieve($q->cookie(-name => 'sess_id')); #etcetera

    =cut
    --Brent Dax
    There is no sig.

Re: Using Sessions between perl and php.
by Anonymous Monk on Jan 19, 2008 at 12:50 UTC
    Thank you very much for this tutorial. I have been working with perl & php separtely for a number of years and have a perl written web app. I decided that perl wasn't doing a good job with one function and I knew the php handled this function flawlessly so I decided to write it in PHP. Then I ran into the session problem. I have been racking my brain for a few days trying to figure out a simple way to do this without rewriting the Storable freeze/thaw function in PHP. Sometimes the simplest solutions are the hardest to think of. Thanks, you have provided a reasonable solution implementable in a short period of time and will work reliably. Also, this is the only post that I have found that even discusses this issue. 8 years later, this page is still relevant. Thanks, greanie

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://140131]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others avoiding work at the Monastery: (5)
As of 2024-03-28 11:32 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found