Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass $_SESSION variables to a websocket server?

I have searched a lot on the web but didn't find a useful clue.

I have a websocket server and a web server running together on my local machine.

I need to pass $_SESSION data to the websocket server when a client connects to it using the browser API 'new WebSocket("ws://localhost")' (the request is send to the websocket using a reverse proxy, which knows it when receives requests with an 'Upgrade' header).

The point is that the clients successfully connect to the ws server, but I need to recover also their SESSION data using the $_SESSION variables setted by the HTTP web server.

Actually my situation is this (I am using the Ratchet library):

use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use MyApp\MyAppClassChat;

require dirname(__DIR__) . '/vendor/autoload.php';

$server = IoServer::factory(new HttpServer(new WsServer(new MyAppClass())), 8080);
$server->run();

The MyAppClass is very simple:

 <?php
namespace MyAppClass;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class MyAppClass implements MessageComponentInterface {

    protected $clients;

    public function __construct() {
        $this->clients = new \SplObjectStorage;
    }

    public function onOpen(ConnectionInterface $conn) {
            /* I would like to put recover the session infos of the clients here
               but the session_start() call returns an empty array ($_SESSION variables have been previuosly set by the web server)*/
        session_start();
        var_dump($_SESSION) // empty array...
        echo "New connection! ({$conn->resourceId})\n";
    }

    public function onMessage(ConnectionInterface $from, $msg) {
        $numberOfReceivers = count($this->clients) -1;
        echo sprintf('Connection %d sending message "%s" to %d other connection%s' . "\n", $from->resourceId, $msg, 
                                 $numberOfReceivers, $numberOfReceivers == 1 ? '' : 's');

        $this->clients->rewind();
        while ($this->clients->valid())
        {
            $client = $this->clients->current();
            if ($client !== $from) {
                $client->send($msg);
            }
            $this->clients->next();
        }
    }

    public function onClose(ConnectionInterface $conn) {
        $this->clients->detach($conn);
        echo "Connection {$conn->resourceId} has disconnected\n";
    }

    public function onError(ConnectionInterface $conn, \Exception $e) {
        echo "An error has occurred: {$e->getMessage()}\n";
        $conn->close();
    }
}

Is there a way to do that with my actual layout or should I configure apache in order to use mod_proxy_wstunnel module?

Thanks for help!!!

like image 435
tonix Avatar asked Apr 22 '14 21:04

tonix


3 Answers

As other StackOverflow answers show (Ratchet without Symfony session, Starting a session within a ratchet websocket connection), there is no way to directly share the $_SESSION variable between Apache and the Ratchet process. It is possible, however, to start a session with the Apache server and then access the session cookie within the Ratchet code.

Apache server's index.html starts the session:

<?php
// Get the session ID.
$ses_id = session_id();
if (empty($ses_id)) {
    session_start();
    $ses_id = session_id();
}
?><!DOCTYPE html> ...

Ratchet MessageComponentInterface code accesses the session token:

public function onMessage(ConnectionInterface $from, $msg) {
    $sessionId = $from->WebSocket->request->getCookies()['PHPSESSID'];
    # Do stuff with the token...
}

Once both servers know the user's session token, they can use the token to share information through a MySQL database (which is what I do):

    # Access session data from a database:
    $stmt = $this->mysqli->prepare("SELECT * FROM users WHERE cookie=?");
    $stmt->bind_param('s', $sessionId);
    $stmt->execute();
    $result = $stmt->get_result();

Alternatively, you could do a more exotic form of inter-process communication:

    # Ratchet server:
    $opts = array(
        'http'=>array(
            'method'=>'GET',
            'header'=>"Cookie: PHPSESSID=$sessionId\r\n"
        )
    );
    $context = stream_context_create($opts);
    $json = file_get_contents('http://localhost:80/get_session_info.php', false, $context);
    $session_data = json_decode($json);

    # Apache server's get_session_info.php
    # Note: restrict access to this path so that remote users can't dump
    # their own session data.
    echo json_encode($_SESSION);
like image 70
dmiller309 Avatar answered Nov 15 '22 17:11

dmiller309


This might seem a bit hacky but it's the only way I could figure out how to accomplish this.

Assuming you already know the directory and sessionId you can read the data from the session file directly using session_encode(), session_decode() as follows. My session files are prefixed with sess_ which may not be the case for others so keep that in mind. Note that this will save and then restore any existing $_SESSION data after extracting the variables from the session file:

$contents = file_get_contents($sessionDir . 'sess_' . $sessionId);
$sessionData = $this->decodeSession($contents);
return $sessionData;

private function decodeSession($sessionString)
{
   $currentSession = session_encode();
   foreach ($_SESSION as $key => $value){
     unset($_SESSION[$key]);
   }
   session_decode($sessionString);
   $sessionVariables = $_SESSION;
   foreach ($_SESSION as $key => $value){
     unset($_SESSION[$key]);
   }
   session_decode($currentSession);
   return $sessionVariables;
}
like image 35
fred Avatar answered Nov 15 '22 17:11

fred


To complete the answers of dmiller309 and fred, I can't comment yet :(

I wouldn't store the session in database, I would simply use the directory that save the sessions, searching for the one that interest me, using session_save_path().

public function onMessage(ConnectionInterface $from, $msg) {
    $session_file = session_save_path()."/sess_".$from->WebSocket->request->getCookies()['PHPSESSID'];
    if(!file_exists($session_file))
        // The session doesn't exist
        return ;
    $content = file_get_contents($session_file);
    $session_id = "0";
    foreach($sections = explode(";", $content) as $k => $section) {
        $data = explode("|", $section, 2);
        if(isset($data[0]) and $data[0] == "id" and isset($data[1]) and substr_count($data[1], '"') == 2) {
            $session_id = substr($data[1], strpos($data[1], '"')+1, -1);
            break;
        }
    }
    if($session_id == "0")
        // The session has no "id"
        return ;

    // ID stored in $session_id, equivalent to $_SESSION["id"]
}

Note: All sessions start with "sess_"

Note 2: I stored the session ID in a string, so I get it between quotes.

Note 3: I didn't use session_decode() for safety reason, as the result is stored in $_SESSION, I assume it might result in a Race Condition.

I didn't test, you can let me know if something doesn't work.

Of course, it's possible to do the same for the other values stored in your session.

like image 1
Mike Avatar answered Nov 15 '22 17:11

Mike