Ratchet icon indicating copy to clipboard operation
Ratchet copied to clipboard

Example Needed (Async MySQL + Ratchet)

Open KristienJones opened this issue 4 years ago • 2 comments

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 avatar Oct 11 '20 21:10 KristienJones

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?

KristienJones avatar Oct 12 '20 01:10 KristienJones

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

FeIix avatar Oct 25 '20 08:10 FeIix