简体   繁体   中英

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!!!

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);

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;
}

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM