Jump to content

How to get content into a ReactPHP server?


Go to solution Solved by kicken,

Recommended Posts

A remote client has connected to the following server described at the bottom of this page, has told the server that its GUID is 123456789, and the server has created a map between this GUID and the specific client connection.

Another script index.php is ran through Apache on the same machine as the sockets server.

<?php

$guid=123456789;
$data='{"message":"hello"}';

someFunctionToSendDataToSocketsServer($guid,$data);

Somehow, I would like to have index.php send $guid=123456789 and $data='{"message":"hello"}' to the sockets server, and then execute:

$client=$this->findConnectionByGuid($guid);
$client->write($data);

someFunctionToSendDataToSocketsServer() should be a blocking function up to the sockets server writing the data (but not that the client received it and acknowledged it) and should return true/false (or a 200/400 header, or some other means to indicate status).

 

 

At first, I was thinking of using a ReactPHP HTTP server operated on the same loop as the sockets server.  index.php would then use curl to send it to the HTTP server, the HTTP server on request event would get the data, and if valid format send the data to the client and return a 200 header, and if not valid format, return a 400 header.  ReactPHP, however, says that the HTTP server is not stable.

 

Another option would be to add a sockets client to the server machine and have index.php use that client to send the data to the sockets server.  I don’t think this make sense, however, as index.php is not communicating bi-directional with the sockets server.  Also, it adds a little complication as the sockets server is currently designed to receive connections from remote clients which send the server their unique GUID.  Lastly, from a security standpoint, I don’t want the remote clients to somehow gain privileges available to only index.php.

 

Or maybe a redis queue where index.php places the content in the queue, and it pops out in the server script where it is sent.  I am not sure how the true/false status would work.

 

Or maybe something else altogether?

 

Any advice?

<?php
namespace MyServer;

use React\EventLoop\Factory;
use React\Socket\Server as SocketServer;
use SplObjectStorage;
require 'JSONStream.php';

class Server
{
    private $app,       //The main application
    $url_sockets,       //Host and port for the socket
    $clientList;        //SplObjectStorage

    public function __construct($url_sockets,$app)
    {
        $this->app = $app;
        $this->url_sockets=$url_sockets;
        $this->clientList = new SplObjectStorage();
    }

    public function start() {
        $loop = Factory::create();
        $socket = new SocketServer($loop);
        $socket->on('connection', function (\React\Socket\ConnectionInterface $client){
            $client = new \Kicken\RLGL\JSONStream($client);
            $this->clientList->attach($client, []);

            $client->on('data', function($data) use ($client){
                // The server doesn't know the client's GUID until the client sends this data to the server   
                if($guid=$this->getConnectionID($client)) {
                    $this->app->process($data, $guid, $client);
                }
                elseif($guid=$this->app->getGUID($data)) {
                    $this->addConnection($client, $guid);
                }
            });

            $client->on('close', function($conn) use ($client) {
                if($this->socketClients->contains($client)) {
                    $this->socketClients->detach($client);
                }
            });

            $client->on('error', function($conn) use ($client) {
                $client->close();
            });

            echo "New connection accepted.\r\n";
        });
        
        $socket->listen($this->url_sockets['port'],$this->url_sockets['host']);

        $loop->run();
    }

    private function addConnection($client, $guid)
    {
        $this->clientList[$client]['guid']=$guid;
    }
    private function getConnectionID($client)
    {
        return $this->clientList[$client]['guid'];
    }
    private function findConnectionByGuid($guid)
    {
        //There should be a better way to do this???
        $this->socketClients->rewind();
        while($this->socketClients->valid()) {
            if($guid == $this->socketClients->getInfo()) {
                return $this->socketClients->current();
            }
            $this->socketClients->next();
        }
        return false;
    }
}

 

  • Solution

Your server and it's clients should communicate with some standard data layout. In my RLGL example for example every JSON structure contained a message field that indicated what type of data structure was being sent.

 

You can use that field to then decide what to do with any data you receive. One of those actions could be something to forward data to a given client which is what your index.php script would use.

 

$client->on('data', function($data) use ($client){
    switch ($data['message']){
        case 'register':
            $this->addConnection($client, $data['guid']);
            break;
        case 'forward':
            $forwardClient = $this->findConnectionByGuid($data['guid']);
            $forwardClient->send($data['data']);
            //Somehow determine success/failure and send the result
            //$client->send(['message'=> 'forward-result', 'success' => true]);
            break;
        default:
            $guid = $this->getConnectionID();
            $this->app->process($data, $guid, $client);
            break;
    }
});
Your index.php script can just use the react framework stuff to send your data and receive a response. Something like this:

function sendToClient($guid, $forwardData){
    $success = false;
    $loop = new \React\EventLoop\StreamSelectLoop();
    $socket = new \React\SocketClient\TimeoutConnector(new \React\SocketClient\TcpConnector($loop), 15, $loop);
    $socket->create($server, $port)->then(function($stream){
        $client = new \Kicken\RLGL\JSONStream($stream);
        $stream->on('data', function($data) use (&$success, $loop, $stream, $guid, $forwardData){
            if ($data['message'] == 'forward-result'){
                $success = $data['success'];
                $stream->close();
                $loop->stop();
            }
        });

        $client->send([
            'message' => 'forward'
            , 'guid' => $guid
            , 'data' => $forwardData
        ]);
    });

    //Generic timeout so this function doesn't block forever.
    $loop->addTimer(30, function() use ($loop){
        $loop->stop();
    });

    $loop->run();

    return $success;
}
  • Like 1

b) Redis.

 

Would server need a continuously loop which constantly checks if the queue has data in it?

 

 

Your server and it's clients should communicate with some standard data layout. In my RLGL example for example every JSON structure contained a message field that indicated what type of data structure was being sent.

 

You can use that field to then decide what to do with any data you receive. One of those actions could be something to forward data to a given client which is what your index.php script would use.

 

 ...

 

Your index.php script can just use the react framework stuff to send your data and receive a response. Something like this:

 

 

This seems to make sense, but to make sure I understand, I would like to explain it in my own words.  Index.php creates a new client and connects to the server.  It sends content with message of "forward", and waits for confirmation of message "forward-result".  Upon confirmation or 30 seconds, it stops the loop.  Will stopping the loop and/or the completion of the index.php script kill the connection to the server?

 

 

Also, how would you recommend preventing one of the clients other than index.php which happens to know another's guid from forwarding something to that client?  My thoughts are:

  1. Have index.php include a secret password which case 'forward': checks before forwarding.
  2. Have case 'forward': check that the client is on the same machine.

I like option 2 more.  Agree?  Is it possible?

 

 

Thanks

Would server need a continuously loop which constantly checks if the queue has data in it?

Of sorts. Either it polls for changes in the main loop (if that's possible) or there's a separate PHP process running on the same machine that does blocking checks.

 

But what kicken is describing (the "act as a client to the existing server" option) is better. If you want to lock down the ability to send messages to a client, so that client A can't force a message to client B, then you can always restrict the command to an IP range (ie, localhost and/or LAN).

Will stopping the loop and/or the completion of the index.php script kill the connection to the server?

PHP will close the connection as part of it's shutdown process after the script ends. Just stopping the loop won't immediately close the connection but your script will probably be ending shortly after the loop is stopped so in effect it does.

 

Also, how would you recommend preventing one of the clients other than index.php which happens to know another's guid from forwarding something to that client?

Either of those would work fine. On the server you can get a client's remote address using ConnectionInterface::getRemoteAddress and compare that to 127.0.0.1 (or whatever).

 

I'd probably go with some kind of authentication token that index.php has but the other clients don't. It's easy to do and will make it easier to split your server off to another machine in the future should you ever need too.

I need to add use() to $socket->create and should probably remove some of the use() variables with $stream->on as shown below, right?

function sendToClient($guid, $forwardData){
    $success = false;
    $loop = new \React\EventLoop\StreamSelectLoop();
    $socket = new \React\SocketClient\TimeoutConnector(new \React\SocketClient\TcpConnector($loop), 15, $loop);
    $socket->create($server, $port)->then(function($stream) use ($loop, $guid, $forwardData){
        $client = new \Kicken\RLGL\JSONStream($stream);
        $stream->on('data', function($data) use (&$success, $loop, $stream){
            if ($data['message'] == 'forward-result'){
                $success = $data['success'];
                $stream->close();
                $loop->stop();
            }
        });

        $client->send([
            'message' => 'forward'
            , 'guid' => $guid
            , 'data' => $forwardData
        ]);
    });

    //Generic timeout so this function doesn't block forever.
    $loop->addTimer(30, function() use ($loop){
        $loop->stop();
    });

    $loop->run();

    return $success;
}
This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.