PHP and Node.JS session share using Redis

This article is more an extension to memcache version, but this time using redis server instead of memcache.
You can of course found the basic principle here, with more comments, the main idea remain exactly the same : Share session between PHP and Node.JS using memcache

First step

You need some stuff to perform this tutorial :

  • A PHP instance (of course)
  • A Node.JS instance (of course too)
  • A redis server (for windows, see this link : Prebuild Redis windows)

We need some extra stuff, one for PHP, the other for Node.JS. On Node.JS I personally use the excellent Predis (PHP Redis) wich is a no-install client for Redis. Just copy files somewhere to get it working.
On Node.JS, i use this redis client available by doing simple "npm install redis"

For everything else, i will use here basic config, so Redis server is running on localhost using port 6379.
As i said before, the main idea is exactly the same as memcache, so i’m only putting code here for PHP and Node.JS, feel free to see previous article to get the full idea.

<?php
//First we load the Predis autoloader
require(dirname(__FILE__)."/../Predis/Autoloader.php");
//Registering Predis system
Predis\Autoloader::register();

/**
 * redisSessionHandler class
 * @class			redisSessionHandler
 * @file			redisSessionHandler.class.php
 * @brief			This class is used to store session data with redis, 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 redis, it store in json the session to be used more easily in Node.JS
 */
class redisSessionHandler{
	private $host = "127.0.0.1";
	private $port = 6379;
	private $lifetime = 0;
	private $redis = null;

	/**
	 * Constructor
	*/
	public function __construct(){
		$this->redis = new Predis\Client(array(
			"scheme" => "tcp",
			"host" => $this->host,
			"port" => $this->port
		));
		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->redis->disconnect();
	}

	/**
	 * 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->redis->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 redis was able to write the session data
	*/
	public function write($id, $data){
		$tmp = $_SESSION;
		session_decode($data);
		$new_data = $_SESSION;
		$_SESSION = $tmp;

		$this->redis->set("sessions/{$id}", json_encode($new_data));
		$this->redis->expire("sessions/{$id}", $this->lifetime);
		return true;
	}

	/**
	 * Delete object in session
	 * @param string $id The SESSID to delete
	 * @return boolean True if redis was able delete session data
	*/
	public function destroy($id){
		return $this->redis->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 redisSessionHandler();
?>

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 redisSessionHandler class).
This system will overpass all php.ini config about session handling. Now if you check inside redis, 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 redisSessionHandler 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 redis (wich store JSON session file, and delete them after session max lifetime) inside Node.JS :
First don’t forget to install redis client : npm install redis

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

var app = require("http").createServer(handler),
    fs = require("fs"),
    redis = require("redis"),
    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);

    //Note : to specify host and port : new redis.createClient(HOST, PORT, options)
    //For default version, you don't need to specify host and port, it will use default one
    var clientSession = new redis.createClient();

    clientSession.get("sessions/"+cookieManager.get("PHPSESSID"), function(error, result){
        if(error){
            console.log("error : "+error);
        }
        if(result.toString() != ""){
            console.log("result exist");
            console.log(result.toString());
        }else{
            console.log("session does not exist");
        }
    });
}

To get the cookie module (./cookie.js), please refer to previous article.

Now it’s almost finish, Node.JS and PHP are both linked to redis, 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 redis expire 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

10 commentaires

  1. Enrico

    there seems to be a problem with the logout process on PHP. here’s the error:

    Fatal error: Uncaught exception ‘Predis\ClientException’ with message "delete’ is not a registered Redis command’

    What could we do?

    • deisss

      I will check exactly what happens, maybe they update the Predis library…

      For now if you check Redis command list, you will see "DEL" command to delete a stored value
      http://redis.io/commands

      So maybe you can try replacing delete by del…

      For now you still have two possibilities, you can change Predis to http://code.google.com/p/php5-redis/ (I change for this one because it’s a lot more lighter one, and it is doing the same stuff… Just faster…)
      I can post a fix for this one, i’m using this one right now.
      Or you can try to check if Predis support a direct command access (like you can send command manually)…

      Again, will check what’s happening with Predis !

  2. deisss

    Here is the phpRedis version :

    redis = new Redis(REDIS_HOST, REDIS_PORT);

    $version = explode(‘.’, PHP_VERSION);
    $major = intval($version[0]) + intval($version[1])/10;

    //Creating a use case corresponding of php version
    if($major redis->disconnect();
    }

    /**
    * Open the session handler, set the lifetime ot session.gc_maxlifetime
    * @param string $save_path The path to save (unused)
    * @param string $session_name The session’s name (unused)
    * @return boolean True if everything succeed
    */
    public function open($save_path, $session_name){
    $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->redis->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 redis was able to write the session data
    */
    public function write($id, $data){
    $tmp = $_SESSION;
    session_decode($data);
    $new_data = $_SESSION;
    $_SESSION = $tmp;

    $this->redis->set("sessions/{$id}", json_encode($new_data));
    $this->redis->expire("sessions/{$id}", $this->lifetime);
    return true;
    }

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

    /**
    * Close gc
    * @param integer $maxlifetime The garbage collector lifetime (unused)
    * @return boolean Always true
    */
    public function gc($maxlifetime){
    return true;
    }

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

  3. :) thanks for your previous answer.so sorry for asking too many question.is windows redis ready for production dear deisss?

  4. deisss

    Yes and no, the windows version seems to be discontinued so if a security bug is found that can be terrible…

  5. If i add that PHP script before the session_start and in every script which uses sessions, the sessions are saved within the php scripts, but once navigate the page, the session is empty. How to solve?

    • deisss

      It probably means that between your Redis instance and PHP, message are not send or not retrieved… Do you have any error message somewhere ?

  6. It is unclear how I save the session data to Redis on the PHP side.

    I am using this in WordPress, but I see no keys saved in Redis:

    function init_session() {
    require(‘redisSessionHandler.php’);
    session_start();
    echo session_id();
    }
    add_action(‘init’, ‘init_session’, 1);

    What else do I need to do in order to get the session into Redis?

    • deisss

      Hum did you see any redis move (saying you are using it right now ?) It should debug some information saying if PHP is currently reaching or not Redis !

  7. Ping : PHP Session, Memcahe and Suhosin Module | Technical Stuff By Azitabh Ajit

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 bloggers like this: