hi i am writing a simple game with php ,
for this work i use http://socketo.me it have a method "onOpen()" that it is like this code
PHP :
public function onOpen(ConnectionInterface $conn) {
// Store the new connection to send messages to later
$this->clients->attach($conn);
echo "New connection! ({$conn->resourceId})\n";
}
i can access to a ConnectionInterface for each clients that it contain a resourceId or a remoteAddress (ipaddress of the client)
but in my application people first login to the web site then they connect to server but the only thing that i have from the clients on the server is a ConnectionInterface how can i know that each $conn is for which user?
In the documentation on the socketo.me web site, there is this page
http://socketo.me/docs/sessions
Each ConnectionInferface has a SessionProvider object. Their example code
// Inside your MyApp class
public function onOpen($conn) {
$conn->send('Hello ' . $conn->Session->get('name'));
}
The session object will have info regarding the logged in user
Related
I am trying to develop a chat system using Websockets (Ratchet). Until now I have already made a functional PHP based Websocket server that is able to answer on predefined JSON encoded message using the following method.
function onMessage(ConnectionInterface $from, $msg){ ... }
The problem is that I'd like to push messages from the backend database via a background worker/thread to the right client, if something changed in my tables. Is this possible using PHP?
I don't want the client asking the websocket server every 5 minutes to refresh its state as shown below.
{"action":"giveMeUpdates"}
but the webserver should be able to do something like this:
{"status":"newMessages", "messagelist":[...]}
In addition:
class Chat extends ChatActionHandler implements MessageComponentInterface { ... }
this is my class where the ChatActionHandler holds functions for interacting with client requests. With the MessageComponentInterface i can only reply on Socket events like function onOpen(ConnectionInterface $conn). It is runned by the RatchetIO Server:
$server = IoServer::factory(
new Chat(),
8080);
$server->run();
You can attach timer, like cron with
$this->loop->addPeriodicTimer($timeout, function($timer) {});
I am trying to test a scenario, that on the one hand, anonymous users should immediately get a disconnect from a Websocket connection and on the other hand, authenticated users should stay in the websocket connection. The first case is easy testable by using the code down under. The authentication process is not working.
For session storage, I am using Cookie authentication in combination with a database: Symfony PDO Session Storage. It's all working fine, but when it comes to testing the described behaviour by using authentication, I don't know how to authenticate the user in a test. As a client, I am using Pawl asynchronous Websocket client. This looks the following:
\Ratchet\Client\connect('ws://127.0.0.1:8080')->then(function($conn) {
$conn->on('message', function($msg) use ($conn) {
echo "Received: {$msg}\n";
});
$conn->send('Hello World!');
}, function ($e) {
echo "Could not connect: {$e->getMessage()}\n";
});
I know that as a third parameter, I can pass header information to the "connect" method, but I cannot find a way so that the client is connected and the cookie is passed correctly during the ws handshake. I thought of something like:
Authenticate a client by creating an authentication token
I create a new entry in the session table in database with serialized user
I pass the created cookie as a third argument to the connect method
This is the theory I thought that would work, but the user always stays anonym on websocket side. Here the code to the theory so far:
// ...
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class WebsocketTest extends WebTestCase
{
static $closed;
protected function setUp()
{
self::$closed = null;
}
public function testWebsocketConnection()
{
$loop = Factory::create();
$connector = new Connector($loop);
// This user exists in database user tbl
$symfClient = $this->createSession("testuser#test.com");
$connector('ws://127.0.0.1:80', [], ['Origin' => 'http://127.0.0.1', 'Cookie' =>
$symfClient->getContainer()->get('session')->getName() . '='
. $symfClient->getContainer()->get('session')->getId()])
->then(function(WebSocket $conn) use($loop){
$conn->on('close', function($code = null, $reason = null) use($loop) {
self::$closed = true;
$loop->stop();
});
self::$closed = false;
}, function(\Exception $e) use ($loop) {
$this->fail("Websocket connection failed");
$loop->stop();
});
$loop->run();
// Check, that user stayed logged
$this->assertFalse(self::$closed);
}
private function createSession($email)
{
$client = static::createClient();
$container = $client->getContainer();
$session = $container->get('session');
$session->set('logged', true);
$userManager = $container->get('fos_user.user_manager');
$em = $container->get('doctrine.orm.entity_manager');
$loginManager = $container->get('fos_user.security.login_manager');
$firewallName = 'main';
$user = $userManager->findUserByEmail($email);
$loginManager->loginUser($firewallName, $user);
// save the login token into the session and put it in a cookie
$container->get('session')->set('_security_' . $firewallName,
serialize($container->get('security.token_storage')->getToken()));
$container->get('session')->save();
$client->getCookieJar()->set(new Cookie($session->getName(), $session->getId()));
// Create session in database
$pdo = new PDOSessionStorage();
$pdo->setSessId($session->getId());
$pdo->setSessTime(time());
$pdo->setSessData(serialize($container->get('security.token_storage')->getToken()));
$pdo->setSessLifetime(1440);
$em->persist($pdo);
$em->flush();
return $client;
}
}
As config_test.yml, I configured the session the following way:
session:
storage_id: session.storage.mock_file
handler_id: session.handler.pdo
For server side websocket implementation, I am using Ratchet, which is being wrapped by the following Symfony bundle: Gos Websocket Bundle
How to authenticate the user when testing websockets? On websocket server, the user is always something like "anon-15468850625756b3b424c94871115670", but when I test manually, he gets connected correct.
Additional question (secondary): How to test the subscription to topics? (pubsub)
There are no blog entries or anything else about this on the internet.
Update: No one ever functional tested their websockets? Is this unimportant, useless or why can't anyone help on that important topic?
You have a cart before the horse situation here. When you set a cookie on a client connection that cookie is then only sent on subsequent requests (websockets or XHR, GET, POST, etc) provided the cookie restrictions (httpOnly, secure, domain, path, etc) match.
Any cookies available are sent during the initial handshake of the websocket connection. Setting a cookie on an open connection will set the cookie on the client but since the socket is already an open connection and established (post handshake) the server will be blind to those cookies for the duration of that connection.
Some people have had success setting the cookie during the handshake. However, that requires the server and client socket implementations supporting this behavior and passing credentials as get parameters (bad practice).
So I think your only real options are:
handle authentication through XHR or other request before opening a websocket
use the websocket for authentication, but then on successful login:
set your auth cookie
close the existing socket
initiate a new socket from the client (which will then carry your auth cookie)
forget cookies entirely and handle an authentication exchange on the server based on the request/resource ID for the open connection.
If you choose the last option you could still set the cookie and look for the cookie to restore connections on reconnects.
(Note: I've intentionally put non adequate websocket tag here, as it's best chance for WebSocket expert folks to know architecture of Ratchet).
I'm up for implementing HTML5 server side events, and what I need is server side solution. Since hanging Apache's one process per connection (connection pool limit, memory consumption...) is out of consideration I was hoping that Ratchet project can be of help, since it's most maintained project and they have http server coupled along with other components.
My question is: how can I use it? Not for upgrading http request (default usage), but for serving dynamically generated content.
What have I tried so far?
installed Ratchet as explained in tutorial
tested WebSocket functionality - works properly
followed very basic set of instructions given on page that describes http server component:
/bin/http-server.php
use Ratchet\Http\HttpServer;
use Ratchet\Server\IoServer;
require dirname(__DIR__) . '/vendor/autoload.php';
$http = new HttpServer(new MyWebPage);
$server = IoServer::factory($http);
$server->run();
One should not be an expert to figure out that MyWebPage class here needs to be declared in order for server to work, but how?
The Ratchet documentation does not seems to cover this.
Your MyWebPage class needs to implement HttpServerInterface. Since it's just going to be a simple request/response you need to send a response and then close the connection within the onOpen() method of your class:
<?php
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\Response;
use Ratchet\ConnectionInterface;
use Ratchet\Http\HttpServerInterface;
class MyWebPage implements HttpServerInterface
{
protected $response;
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null)
{
$this->response = new Response(200, [
'Content-Type' => 'text/html; charset=utf-8',
]);
$this->response->setBody('Hello World!');
$this->close($conn);
}
public function onClose(ConnectionInterface $conn)
{
}
public function onError(ConnectionInterface $conn, \Exception $e)
{
}
public function onMessage(ConnectionInterface $from, $msg)
{
}
protected function close(ConnectionInterface $conn)
{
$conn->send($this->response);
$conn->close();
}
}
I ended up using the Ratchet\App class instead of Ratchet\Http\HttpServer because it allows you to set up routing among other things, so your /bin/http-server.php would then look like this:
<?php
use Ratchet\App;
require dirname(__DIR__) . '/vendor/autoload.php';
$app = new App('localhost', 8080, '127.0.0.1');
$app->route('/', new MyWebPage(), ['*']);
$app->run();
When you run php bin/http-server.php and visit http://localhost:8080 you should see the Hello World! response in your browser.
This is all you need for a basic request/response system, but it could be extended further by implementing HTML templates and things like that. I've implemented this myself in a little test project which I've uploaded to github along with a lot of other things, including an abstract controller which I can extend for different pages.
Chat server using Ratchet - Basic
Chat server using Ratchet - Advanced
Check the link above. The guy here is using Ratchet to build a real time chat server. He is basically storing usernames initially and then sending/broadcasting to all. You can modify it and check at the time of sending that certain username or uid is active at the moment and send data to them only. You can generate data dynamically and send to particular users or to all. May be this will help.
I'm using the Phalcon flash service to store temporary messages between HTTP redirects.
It worked great until I recently changed to a database session adapter. Now flash messages are being stored in the database but are not being deleted.
I don't want these messages to touch the database so I setup the previous session method in the DI but under a new tempSession entry.
public function initTempSession($options = [])
{
$this->di->set(
'tempsession',
function () {
$tempSession = new PhSession();
$tempSession->start();
return $tempSession;
},
true
);
}
How can I configure the flash service to use this session function, rather than the 'default' database adapter?
I want to use a Ratchet (http://socketo.me) for permanent connection between iPhone Apps and Server. And I need to exchange data between apps and server.
From this example (http://socketo.me/docs/hello-world) I've found out that I have a function onMessage that will be called when the App send a massage to the server and the server could send a response to the App.
But the server also must have an ability to send data to the app without getting data from app. For example, the connection between app and server has been established. Something happened on the server and we need to send a new data to the app. How can I do it and is it possible?
The main question is how can I send data to the App from the server?
Thank you for any help.
That is indeed possible. You need to communicate with the WebSocket server process somehow. You can do that by using some form of message passing, be it RPC or a message queue.
Ratchet itself is based on the React event loop. This means that any form of communication with Ratchet must be integrated with that event loop. On the React homepage you can see some of the integrations that already exist:
Predis/Async (you can use redis pub/sub for message passing)
DNode-PHP (dnode is a TCP based RPC protocol)
React/ZMQ (integrates the event loop with ZeroMQ)
React/Stomp (implementation of the STOMP protocol, allowing you to talk to a message such as RabbitMQ)
JCook21/ReactAMQP (AMQP bindings for React PHP)
In the Ratchet documentation there is a tutorial on how to use React/ZMQ in order to push messages from anywhere to your WebSocket server.
Ratchet also implements WAMP, which includes PubSub. So your clients can subscribe to some topics, and you can have other clients (running i.e. on your backend infrastructure) publish to those topics. You could i.e. have an AutobahnPython based client publishing via Ratchet to an AutobahnAndroid based mobile app or AutobahnJS based HTML5 client.
I had the exact same question and here is what I did.
Based on the hello world tutorial, I have substituted SplObjectStorage with an array. Before presenting my modifications, I'd like to comment that if you followed through that tutorial and understood it, the only thing that prevented you from arriving at this solution yourself is probably not knowing what SplObjectStorage is.
class Chat implements MessageComponentInterface {
protected $clients;
public function __construct() {
$this->clients = array();
}
public function onOpen(ConnectionInterface $conn) {
// Store the new connection to send messages to later
$this->clients[$conn->resourceId] = $conn;
echo "New connection! ({$conn->resourceId})\n";
}
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 $key => $client) {
if ($from !== $client) {
// The sender is not the receiver, send to each client connected
$client->send($msg);
}
}
// Send a message to a known resourceId (in this example the sender)
$client = $this->clients[$from->resourceId];
$client->send("Message successfully sent to $numRecv users.");
}
public function onClose(ConnectionInterface $conn) {
// The connection is closed, remove it, as we can no longer send it messages
unset($this->clients[$conn->resourceId]);
echo "Connection {$conn->resourceId} has disconnected\n";
}
public function onError(ConnectionInterface $conn, \Exception $e) {
echo "An error has occurred: {$e->getMessage()}\n";
$conn->close();
}
}
Of course to make it really useful you may also want to add in a DB connection, and store/retrieve those resourceIds.