ratchetphp / Ratchet

Asynchronous WebSocket server
http://socketo.me
MIT License
6.28k stars 743 forks source link

Example Needed (Async MySQL + Ratchet) #836

Open KristienJones opened 4 years ago

KristienJones commented 4 years ago

Hi. I need some examples of using Async MySQL with an IoServer. I'll be using https://github.com/friends-of-reactphp/mysql most likely, but I can't get it to work correctly with Ratchet.

My Scenario: I need to authenticate connections using tokens that are stored on a database. The user connects with a token. Then once connected, the server authenticates and allows the connection to continue.

I'm entirely new to Ratchet and using composer also, so i'm clearly a little lost. I've spent the last day trying to figure it out but to no avail.

Below is my working code (Non-Async) server.php:

       use Ratchet\Server\IoServer;
    use Ratchet\Http\HttpServer;
    use Ratchet\WebSocket\WsServer;
    use MyApp\Chat;
    require dirname(__DIR__) . '/vendor/autoload.php';

    $server = IoServer::factory(
        new HttpServer(
            new WsServer(
                new Chat()
            )
        ),
        8080
    );

    $server->run();

Chat.php

    namespace MyApp;
    use Ratchet\MessageComponentInterface;
    use Ratchet\ConnectionInterface;
    use mysqli;

    class Chat implements MessageComponentInterface {
        protected $clients;
        protected $publicKey;
        protected $privateKey;
        protected $db;

        public function __construct() {
            $this->publicKey = openssl_pkey_get_public(file_get_contents(dirname(__DIR__).'/certificates/webSocket/webSocket.pem'));
            $this->privateKey = openssl_pkey_get_private(file_get_contents(dirname(__DIR__).'/certificates/webSocket/webSocket.pem'));

            $this->clients = new \SplObjectStorage;
            echo "Server Started.";
        }

        public function onOpen(ConnectionInterface $conn) {
            $this->clients->attach($conn);
            echo "New connection! ({$conn->resourceId})\n";
            $authFail = false;

            $this->db = new mysqli("localhost","root","","lewisjones"); 
            if($this->db){
                $querystring = $conn->httpRequest->getUri()->getQuery();
                parse_str($querystring,$queryarray);

                if(!isset($queryarray['token'])){
                    $authFail = true;
                } else {
                    $getSession = $this->query("select token from wsSessions where userID = ? limit 1",array(1));
                    if(!is_array($getSession)){
                        $authFail = true;
                    } else {
                        $verify = openssl_verify($getSession[0]["token"], base64_decode(base64_decode($queryarray['token'])), $this->publicKey, OPENSSL_ALGO_SHA256);
                        if($verify != 1) {
                            $authFail = true;
                        }
                    }
                    $this->query("delete from wsSessions where userID = ?",array(1));
                }
                $this->db->close();
            } else {
                $authFail = true;
            }

            if($authFail){
                foreach ($this->clients as $client) {
                    if ($conn->resourceId !== $client) {
                        $client->send("Authentication failed! Connection Closed.");
                        $conn->close();
                    }
                }
            }
        }

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

            foreach ($this->clients as $client) {
                if ($from !== $client) {
                    $client->send($msg);
                }
            }
        }

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

        private function query($sql,$params = null){
            $types = null;
            if(is_array($params)){
                $types = "";
                foreach($params as $key => $value){
                    if(is_int($value)){
                        $types .= "i";
                    } else {
                        if(is_float($value)){
                            $types .= "d";
                        } else {
                            $types .= "s";
                        }
                    }
                }
            }

            if($stmt = $this->db->prepare($sql)){
                if($types && $params){
                    $bind_names[] = $types;
                    for ($i=0; $i<count($params);$i++){
                        $bind_name = 'bind' . $i;
                        $$bind_name = $params[$i];
                        $bind_names[] = &$$bind_name;
                    }
                    call_user_func_array(array($stmt,'bind_param'),$bind_names);
                }

                $stmt->execute();
                $result = $stmt->get_result();

                if (!empty($result) && $result->num_rows > 0) {
                    $returnArray = array();
                    while ($row = $result->fetch_assoc()) {
                        $returnArray[] = $row;
                    }
                    return $returnArray;
                }

                if($stmt){
                    return true;
                } 
                return false;

                $stmt->free_result();
                $stmt->close();
            }
            return false;
        }
    }

If anyone can point me in the right direction, that would be appreciated. Thanks.

KristienJones commented 4 years ago

OK. Update, I've managed to get it working but i've hit another wall.

Due to the nature of it being asynchronous, I cannot seem to manage variables correctly (Which I need in order to close a connection if the auth fails)

This is what I have so far:

public function onOpen(ConnectionInterface $conn) {
            $this->clients->attach($conn);
            echo "New connection! ({$conn->resourceId})\n";

            $connection = $this->factory->createLazyConnection($this->uri);
            $connection->query("select token from wsSessions where userID = ? limit 1",[1])->then(function(ConnectionInterface $conn,QueryResult $command) {
                //logic here, if not authorized, close connection
                $conn->close();
            });
            $connection->quit();
        }

The problem I have, is that I cannot close the connection. $conn doesn't seem to parse correctly. And I can't update global variables and do the logic outside of the query function because it's asynchronous.

Any advice?

FeIix commented 4 years ago

Hi, I think you just need to add use($conn) to use it in your anonymous function scope.

Like this

$connection->query("select token from wsSessions where userID = ? limit 1", [1])
    ->then(function(ConnectionInterface $conn,QueryResult $command) use($conn) { // <- here
        //logic here, if not authorized, close connection
        $conn->close();
    });