PHP and Node.JS session share using memcache

I was needing some real time performance using PHP. After searching and creating « patched » systems, i’ve to admit there is no real good system using only PHP.

First step

The best way i found was using long polling system using a file system + Mysql to store current system (file, when user first connect), and last commands sended (Mysql, for user already logged who need only small updates). The system still have some trouble because of lost messages while transfering and sometimes time difference while using clearstatcache.

You can found a really basic example of such kind of system here.

This system in general is not stable for production. So I decide to extend existing PHP system with Node.JS + socket.io.

Extending PHP

Because the PHP system was already finished, and only few parts use real time, the idea was using a share system between PHP and Node.JS. The main problem was in fact to share session from PHP to Node.JS.

Understanding PHP’s session

PHP session are not easy to change/manipulate when serialized, basically PHP use a handler to store data, a stored sessions will look like this :
user_id|s:1:"1";password|s:0:"";firstname|s:7:"Charles";

Basically it is : key | type : length : value ;
In case of integer, it will be type : value directly.
The problem is that, this serialize system is not so easy to manipulate in PHP, and in Node.JS also. PHP use a specific serializer/unserializer called session_decode and session_encode for that. And Node.JS got nothing. The problem is pretty simple, imagine a string variable using a « ; » inside. In such case you just can’t use a split(« ; ») (like many link on internet suggest) because you will cut the variable content. That’s why we should change the storing system to get a more easy-to-change format for both PHP and Node.JS : JSON.

PHP Session Handler

There is many way to change the session handler, but in fact only one is enough flexible to change the stored result. If you use directly php.ini to set a different storage (like memcache), you will not get what we want, because session will still be stored like previous system but in memcache, and you cannot change anything to that. PHP provide a more flexible system, using the session_set_save_handler. This function allow to manipule each basic operation on session storage : open/read/write/delete/close and gc (garbage collector).
By the way, we must have a shared system flexible for Node.JS and PHP, basically there is two main system : memcache and SQL storage, which are both pretty easy to use in Node and PHP.
From now, I consider you got PHP/Node.JS & memcache configured, with memcache running on port 11211 on localhost, PHP is linked to memcache using php-memcache. There is plenty tutorials on internet, for every system (Windows, linux/OSX).

Custom Handler to share with Node.JS

The problem with session_set_save_handler function, is that save function already got session encoded data version passed (so we recieve the encoded session), and read function must retrieve the same already-encoded version, so we must unserialize before storing, and re-serialize after storing.
This class already do the trick properly :

<?php
/**
 * memcacheSessionHandler class
 * @class			memcacheSessionHandler
 * @file			memcacheSessionHandler.class.php
 * @brief			This class is used to store session data with memcache, it store in json the session to be used more easily in Node.JS
 * @version			0.1
 * @date			2012-04-11
 * @author			Deisss
 * @licence			LGPLv3
 * This class is used to store session data with memcache, it store in json the session to be used more easily in Node.JS
 */
class memcacheSessionHandler{
    private $host = "localhost";
    private $port = 11211;
    private $lifetime = 0;
    private $memcache = null;

    /**
     * Constructor
     */
    public function __construct(){
        $this->memcache = new Memcache;
        $this->memcache->connect($this->host, $this->port) or die("Error : Memcache is not ready");
        session_set_save_handler(
            array($this, "open"),
            array($this, "close"),
            array($this, "read"),
            array($this, "write"),
            array($this, "destroy"),
            array($this, "gc")
        );
    }

    /**
     * Destructor
     */
    public function __destruct(){
        session_write_close();
        $this->memcache->close();
    }

    /**
     * Open the session handler, set the lifetime ot session.gc_maxlifetime
     * @return boolean True if everything succeed
     */
    public function open(){
        $this->lifetime = ini_get('session.gc_maxlifetime');
        return true;
    }

    /**
     * Read the id
     * @param string $id The SESSID to search for
     * @return string The session saved previously
     */
    public function read($id){
        $tmp = $_SESSION;
        $_SESSION = json_decode($this->memcache->get("sessions/{$id}"), true);
        if(isset($_SESSION) && !empty($_SESSION) && $_SESSION != null){
            $new_data = session_encode();
            $_SESSION = $tmp;
            return $new_data;
        }else{
            return "";
        }
    }

    /**
     * Write the session data, convert to json before storing
     * @param string $id The SESSID to save
     * @param string $data The data to store, already serialized by PHP
     * @return boolean True if memcached was able to write the session data
     */
    public function write($id, $data){
        $tmp = $_SESSION;
        session_decode($data);
        $new_data = $_SESSION;
        $_SESSION = $tmp;
        return $this->memcache->set("sessions/{$id}", json_encode($new_data), 0, $this->lifetime);
    }

    /**
     * Delete object in session
     * @param string $id The SESSID to delete
     * @return boolean True if memcached was able delete session data
     */
    public function destroy($id){
        return $this->memcache->delete("sessions/{$id}");
    }

    /**
     * Close gc
     * @return boolean Always true
     */
    public function gc(){
        return true;
    }

    /**
     * Close session
     * @return boolean Always true
     */
    public function close(){
        return true;
    }
}

new memcacheSessionHandler();
?>

You just need to include this script at the beginning of each script wich use session, it must be included before every session_start call (it start by itself memcacheSessionHandler class).
This system will overpass all php.ini config about session handling. Now if you check inside memcache, the session are now stored in JSON. It will serialize/unserialize to still let PHP system using his own way. It is totally transparent for PHP.
Be carefull session_set_save_handler change with PHP5.4, so in this case you have to modify little bit this class :

class memcacheSessionHandler implements SessionHandlerInterface{

For PHP5.4 now there is interface to implements. There is also session_set_save_handler little bit different (replace on __construct function) :

session_set_save_handler(&$this, true);

Instead of long code function… This is enough for support on PHP5.4.

Node.JS Part

Now we just need to use memcache (wich store JSON session file, and delete them after session max lifetime) inside Node.JS :
First don’t forget to install memcache : npm install memcache

Here is a basic example how to use PHP Session (using cookie session ID) :

var app = require("http").createServer(handler),
    fs = require("fs"),
    memcache = require("memcache"),
    co = require("./cookie.js");

app.listen(7070);

//On client incomming, we send back index.html
function handler(req, res){
    fs.readFile(__dirname + "/index.html", function(err, data){
        if(err){
            res.writeHead(500);
            return res.end("Error loading index.html");
        }else{
            res.writeHead(200);
            res.end(data);
        }
    });


    //Using php session to retrieve important data from user
    var cookieManager = new co.cookie(req.headers.cookie);

    var client = new memcache.Client(11211, "localhost");
    client.connect();

    client.get("sessions/"+cookieManager.get("PHPSESSID"), function(error, result){
        console.log("error : "+error);
        if(typeof(error)==="undefined"){
            var session = JSON.parse(result);
        }
    });
}

You need to get also this cookie module (./cookie.js) :

//Directly send cookie to system, if it's node.js handler, send :
//request.headers.cookie
//If it's socket.io cookie, send :
//client.request.headers.cookie
module.exports.cookie = function(co){
    this.cookies = {};
    co && co.split(';').forEach(function(cookie){
        var parts = cookie.split('=');
        this.cookies[parts[0].trim()] = (parts[1] || '').trim();
    }.bind(this));

    //Retrieve all cookies available
    this.list = function(){
        return this.cookies;
    };

    //Retrieve a key/value pair
    this.get = function(key){
        if(this.cookies[key]){
            return this.cookies[key];
        }else{
            return {};
        }
    };

    //Retrieve a list of key/value pair
    this.getList = function(map){
        var cookieRet = {};
        for(var i=0; i<map.length; i++){
            if(this.cookies[map[i]]){
                cookieRet[map[i]] = this.cookies[map[i]];
            }
        }
        return cookieRet;
    };
};

Now it’s almost finish, Node.JS and PHP are both linked to memcache, and share session in JSON. When one (PHP in fact), disconnect, the other (Node.JS) will do the same because PHP continue to keep hand on memcache like it use to do with session (so login & logout should stay on PHP side).
Don’t forget on Node.JS the PHPSESSID => may be different if you use a different session name on PHP.ini.

About these ads

19 Commentaires

  1. can you answer this question which i asked?i think some problem in code or maybe…i dont know.i am noob in node.thanks for your great article

  2. deisss

    I reply to that question on stack directly ;)

  3. thanks for this great article dear deisss but there is some problem in this case how should we keep our sessions alive in memcache as long as user connected to our app.js?refreshing our session to dont allow this session to getting expired?

  4. deisss

    Take care of that line :

    $this->lifetime = ini_get(‘session.gc_maxlifetime’);

    The system will search for session.gx_maxlifetime in php.ini, which is the « official » session time expire of your php instance. It’s in seconds, and by default it’s 1440 (so 25min approx.).

    This is how it works :
    PHP will drive the session : when the user connect for first time (session_start), it will call get, get nothing, then call set.
    Then the user call another page, the system will call get. BUT, it will also call set when session_write_close is called, which is always done at the end by PHP himself. This is because if you change $_SESSION, at one time PHP can’t get if you change it or not, so everytime there is it will rewrite to be sure.

    So everytime user connect on PHP, the expire time is renew, always. Because while the session exist, the id remain the same (indeed).

    So the session will expire when the user stop using PHP side : memcache « set » use the expire parameter, so if PHP don’t call memcache again to renew, memcache will destroy the session by itself, making Node.JS stop using session also.

    After you need to take care on Node.JS side to not keep too long session. So on every command on Node.JS, you should check that command is ok (check Memcache), like PHP automatically do on the other side. You can have a trouble here if you don’t refresh session on Node.JS side too, so by default you should catch it again everytime user call for new request on Node.JS

  5. deisss

    On the other side (Node.JS), if you want to refresh data, take the session, and call the memcache set from Node.JS, i Recommand to use the same session expire as PHP do, so in this case i will do like that :

    On memcacheSessionHandler « open » function, send to memcache the value of session.gx_maxlifetime using set, with NO expire : $this->memcache->set(« sessions/lifetime », $this->lifetime, 0, 0);

    On Node.JS get that value, call memcache set with same expire. Then both (Node.JS and PHP), can keep the session alive, not only PHP.

  6. Great!!
    i did like that and it works perfectly.but i read in some article if we have server crash we lost all sessions and users must re login again in this method :(.now is there any cookie way or something like to keep users logged in if our server crashes?after that i thought about storing sessions in mysql and read session_ids from node to keep user logged_in if server crashed.but i need a session save handler which store session data in database in json format

  7. deisss

    It’s becoming too heavy stuff for that behaviour…

    Basically in such case you just need to set some replica somewhere. But, memcache is definitely not a good candidate for replication set.

    In this case, i may recommand you that one : quite the same code, but using Redis (a better idea for that) instead of memcache. They got both same command-like interface :

    http://simplapi.wordpress.com/2012/04/13/php-and-node-js-session-share-redi/

    But Redis got a better master/slave idea than memcache do.

    Doing so, if a memcache crash, then you have a replica to keep session alive ;)

  8. you are life saver deisss…Great!!!

  9. Rob

    Thanks for that post. It’s great solution!

  10. kobra007

    thx man for this article but I wonder if sharing session is a good practice to communicate between 2 systems (PHP and Node) and if sessions are suffisant authentication mechanism to manage users? wath s your opinion on that ? thx

    • deisss

      In fact if you looks close, it’s not communicating at all : PHP does the job it use to do in normal situation, Node read somewhere…

      So, they are not communicate…

      But if you want to communicate, the PubSub from redis is a better way in this case, and yes, a good idea. Note that Node.JS always run, while PHP usually not, so it will be probably unidirectionnal communication ;)

      About Auth, it depends on yours needs, but usually yes it’s enough security !

  11. bill

    Good tutorial!!!
    I’ve got a question, if I want the php and nodejs to do the single sign on, how to achieve this? Better do most of the job on php side.

    • deisss

      Yes if you check completely this article, Node.JS is a kind of « plug » to PHP, only PHP is really doing the job here, Node.JS just check if it can or not do some stuff regarding stored session by PHP…

  12. This is a great aritlce and absolutely what i was looking for. I would only suggest one thing that I think would be much better to use that instead calling the class you suggested and storing sessions in JSON format, I think it would be better to let the PHP store sessions in format it stores and on Node.JS side use PHP-unserialize module by NPM to parse PHP array to JSON. This will be significant in many ways first that Node is much faster to parse into JSON array as compared to PHP parsing into JSON format, second PHP is still able to handle the session in older way without doing extra over work.

  13. Great tutorial…This is send message from php to nodejs server via redis..Is there any hint to send data from nodejs server to php via redis ??

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

Suivre

Recevez les nouvelles publications par mail.

%d blogueurs aiment cette page :