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.
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?
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 !
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;
}
}
?>
Yes and no, the windows version seems to be discontinued so if a security bug is found that can be terrible…
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?
It probably means that between your Redis instance and PHP, message are not send or not retrieved… Do you have any error message somewhere ?