I am trying to subscribe to a channel of an api. I tried using 'ratchetphp/Pawl' and Laravel and this is my code:
$loop = Factory::create();
$connector = new Connector($loop);
$connector('wss://api.poloniex.com')
->then(function(WebSocket $conn) {
$conn->send('{"event": "subscribe","channel":"ticker","pair": "BTC_ETH"}');
$conn->on('message', function(MessageInterface $msg) use ($conn) {
var_dump($msg);
});
}, function(\Exception $e) use ($loop) {
/** hard error */
echo "Could not connect: {$e->getMessage()}\n";
$loop->stop();
});
$loop->run();
However, I am receiving error:
This server only speaks WebSocket subprotocols wamp.2.cbor.batched, wamp.2.cbor, wamp.2.msgpack.batched, wamp.2.msgpack, wamp.2.json.batched, wamp.2.json
I also tried $connector('wss://api.poloniex.com', ['wamp']) and $connector('wss://api.poloniex.com', ['wamp2']) but same error persists.
What am I doing wrong?
Related
I have a very simple websocket using PHP and Ratchet libraray.
When a user opens a specific page it sends the users id to my socket and it should update the status for that user (at the moment I'm just logging it in the console), like this:
<input type="hidden" value="'.$account_id.'" id="account_id">
<input type="hidden" value="trial" id="request_type">
<script>
$(document).ready(function(){
var conn = new WebSocket('ws://127.0.0.1:8080');
conn.onopen = function(e){
console.log("Connection Opened!");
var account_id = $("#account_id").val();
var request_type = $("#request_type").val();
var data = {account_id: account_id, request_type: request_type};
conn.send(JSON.stringify(data));
}
conn.onclose = function(e){
console.log("Connection Closed!");
}
conn.onmessage = function(e) {
var data = JSON.parse(e.data);
console.log(data);
};
conn.onerror = function(e){
var data = JSON.parse(e.data);
console.log(data);
}
})
</script>
Then my socket script is as follows:
set_time_limit(0);
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
require dirname(__DIR__) . '../vendor/autoload.php';
class socket implements MessageComponentInterface{
protected $clients;
public function __construct(){
$this->clients = new \SplObjectStorage;
echo 'Server Started.'.PHP_EOL;
}
public function onOpen(ConnectionInterface $socket){
$this->clients->attach($socket);
echo 'New connection '.$socket->resourceId.'!'.PHP_EOL;
}
public function onClose(ConnectionInterface $socket) {
$this->clients->detach($socket);
echo 'Connection '.$socket->resourceId.' has disconnected'.PHP_EOL;
}
public function onError(ConnectionInterface $socket, \Exception $e) {
echo 'An error has occurred: '.$e->getMessage().'!'.PHP_EOL;
$socket->close();
}
public function onMessage(ConnectionInterface $from, $json){
echo 'Connection '.$from->resourceId.' sent '.$json.PHP_EOL;
$data = json_decode($json, true);
$account_id = $data['account_id'];
$request_type = $data['request_type'];
try {
$conn = new PDO("mysql:host=".$db_host.";port:".$db_port.";dbname=".$db_name."", $db_user, $db_pass);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}catch(PDOException $e){
echo $e->getMessage();
}
foreach ($this->clients as $client) {
if ($from->resourceId == $client->resourceId) {
if($request_type == 'trial'){
// while(true){
$response_array= [];
$stmt = $conn->prepare("SELECT * FROM table WHERE account_id=:account_id AND last_status_change=now()");
$stmt->bindParam(':account_id', $account_id);
$stmt->execute();
$result = $stmt->setFetchMode(PDO::FETCH_ASSOC);
foreach($stmt->fetchAll() as $key=>$value) {
$response_array[$key] = $value;
}
if(!empty($response_array)){
foreach($response_array as $item){
$status = $item['status'];
}
$response = array(
'account_id' => $account_id,
'status' => $status
);
var_dump($response);
$client->send(json_encode($response));
}
// sleep(5);
// }
}
}
}
}
}
$server = IoServer::factory(
new HttpServer(
new WsServer(
new socket()
)
),
8080
);
$server->run();
As it stands it works as expected, but only gives the current status if the status changed at the time when the page was loaded and I will see the status in the console, as soon as I un-comment the while() loop to actually keep checking the status for updates, my socket will do the var_dump() of the result in the command line when there is a status change but nothing gets logged in the client.
I'm new to websockets, I had been doing long polling by having an interval in JS that was sending a fetch() to a PHP script that got the latest DB results but it wasn't very efficient and was causing issues when a large number of clients were active and constantly making requests to the file which was in turn slowing down the DB. So I'm not sure why the while() loop is affecting it like this or if I am even going about this the right way.
A while loop is not how it works. It will block stuff and infinitely and unnecessarily consume resources.
What you want is addPeriodicTimer().
Check periodically for clients that need updates.
Add to your bootstrapping something like this:
$reactEventLoop->addPeriodicTimer(5, function() use $messageHandler, $server {
// Fetch all changed clients at once and update their status
$clientsToUpdate = getUpdatedClients($server->app->clients);
foreach ($clientsToUpdate as $client) {
$client->send(json_encode($response));
}
});
This is much more lightweight than any other method, as you can
Fetch N clients status with a single prepared database query
Update only changed clients periodically
Not put your app in a blocking state
Other resources on Stackoverflow will help you to find the right spot:
How do I access the ratchet php periodic loop and client sending inside app?
Periodically sending messages to clients in Ratchet
replace this line if ($from->resourceId == $client->resourceId) { with if ($from == $client) { this change may look simple but in the example Chat class provided by php ratchet in order avoid sending the message to the sender they have a condition to send messages to clients except the sender, they compared like this if ($from == $client) { only not only an resourceId the entire object itself!
you should be using addPeriodicTimer from Ratchet, although you have to make $clients public in order to place the timer.
Maybe you can place it inside the class and still be private, but I am not sure if it could initiate a timer for every client.
Anyway as you can see, you can create another public function that will actually do the job in the periodic timer(just like while loop)
and then call it once the client is connected and multiple times inside the timerloop,
for that I created also a public account_ids to keep truck of the account ids
Give it a try and let me know
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
require dirname(__DIR__) . '../vendor/autoload.php';
class socket implements MessageComponentInterface{
public $clients;
public $account_ids;
public function __construct(){
$this->clients = new \SplObjectStorage;
echo 'Server Started.'.PHP_EOL;
}
public function onOpen(ConnectionInterface $socket){
$this->clients->attach($socket);
echo 'New connection '.$socket->resourceId.'!'.PHP_EOL;
}
public function onClose(ConnectionInterface $socket) {
$this->clients->detach($socket);
echo 'Connection '.$socket->resourceId.' has disconnected'.PHP_EOL;
}
public function onError(ConnectionInterface $socket, \Exception $e) {
echo 'An error has occurred: '.$e->getMessage().'!'.PHP_EOL;
$socket->close();
}
public function onMessage(ConnectionInterface $from, $json){
echo 'Connection '.$from->resourceId.' sent '.$json.PHP_EOL;
$data = json_decode($json, true);
$account_id = $data['account_id'];
$request_type = $data['request_type'];
foreach ( $this->clients as $client ) {
if ( $from->resourceId == $client->resourceId ) {
if( $request_type == 'trial'){
$this->account_ids[$client->resourceId] = $account_id;
$this->checkStatus($client, $account_id);
}
}
}
}
public function checkStatus($client, $account_id){
try {
$conn = new PDO("mysql:host=".$db_host.";port:".$db_port.";dbname=".$db_name."", $db_user, $db_pass);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}catch(PDOException $e){
echo $e->getMessage();
}
$response_array= [];
$stmt = $conn->prepare("SELECT * FROM table WHERE account_id=:account_id AND last_status_change=now()");
$stmt->bindParam(':account_id', $account_id);
$stmt->execute();
$result = $stmt->setFetchMode(PDO::FETCH_ASSOC);
foreach($stmt->fetchAll() as $key=>$value) {
$response_array[$key] = $value;
}
if ( !empty($response_array) ) {
foreach($response_array as $item){
$status = $item['status'];
}
$response = array(
'account_id' => $account_id,
'status' => $status
);
var_dump($response);
$client->send(json_encode($response));
}
}
}
$socket = new socket();
$server = IoServer::factory(
new HttpServer(
new WsServer(
$socket
)
),
8080
);
$server->loop->addPeriodicTimer(5, function () use ($socket) {
foreach($socket->clients as $client) {
echo "Connection ".$client->resourceId." check\n";
$socket->checkStatus($client, $socket->account_ids[$client->resourceId]);
}
});
$server->run();
I'm currently attempting to connect to two different socket servers. One of them is essentially an IRC connection, the other is an interface server that I've made myself. These two loops need to be able to communicate with each other, but I'm having difficulty actually sending a message from one connection to another.
Here's what I've been trying as a simplified way of injecting the message, the comments are not very confident because I'm honestly not sure where I'm going wrong:
<?php
require __DIR__ . '/vendor/autoload.php';
// EventLoop the way I understand it is designed to split up your loops and
// run them one after another so you can kind of multithread, even though
// it's just one step of the loop at a time.
$loop = \React\EventLoop\Factory::create();
// Verbose defintion of connectors mainly trying to just gain extra control
// and so I could see how each thing was defined.
$reactConnector = new \React\Socket\Connector($loop, [
'dns' => '8.8.8.8',
'timeout' => 10
]);
$connector = new \Ratchet\Client\Connector($loop, $reactConnector);
// Connect as a client to the development Socket server. This is all just
// from the Pawl github essentially.
//
// The connection is successful every time.
$ws = $connector('ws://0.0.0.0:5069', [], ['Origin' => 'http://localhost']);
->then(function(Ratchet\Client\WebSocket $conn) {
// Simple echo on message received for the test.
$conn->on('message', function(\Ratchet\RFC6455\Messaging\MessageInterface $msg) use ($conn) {
echo "Received: {$msg}\n";
});
$conn->on('close', function($code = null, $reason = null) {
echo "Connection closed ({$code} - {$reason})\n";
});
$conn->send('Hello World!');
}, function(\Exception $e) use ($loop) {
echo "Could not connect: {$e->getMessage()}\n";
$loop->stop();
});
// Instead of including a second socket connector I decided to use a simple
// timer and try to get it to use the client connection above to send
// messages to the socket server.
//
// The problem is here, I can't get the socket server to send a message
// from outside of the ->then();
$loop->addPeriodicTimer(1, function () use ($ws) {
$ws->then(function (Ratchet\Client\WebSocket $conn) {
$conn->send('Figured out?');
});
});
$loop->run();
I'd really like to be able to send messages from one connection the the other through some sort of $ws->send('message');, but I can't for the life of me figure out how.
Ahhh Finally a question I can answer!! I spent most of yesterday working through my own Ratchet/Pawl client that had to have addPeriodicTimer loops to send content at periodic times. It took some poking around but I made it work by placing the $loop->addPeriodicTimer() call INSIDE of the connector block, after the ->then(function(Ratchet\Client\WebSocket $conn) part and before the $conn->on('message'...) calls. Also for the $loop->addPeriodicTimer calls make sure to add the use clause passing in the connection ... and make sure to add the use clause passing in the $loop to the connector.
<?php
require __DIR__ . '/vendor/autoload.php';
// EventLoop the way I understand it is designed to split up your loops and
// run them one after another so you can kind of multithread, even though
// it's just one step of the loop at a time.
$loop = \React\EventLoop\Factory::create();
// Verbose defintion of connectors mainly trying to just gain extra control
// and so I could see how each thing was defined.
$reactConnector = new \React\Socket\Connector($loop, [
'dns' => '8.8.8.8',
'timeout' => 10
]);
$connector = new \Ratchet\Client\Connector($loop, $reactConnector);
// Connect as a client to the development Socket server. This is all just
// from the Pawl github essentially.
//
// The connection is successful every time.
$ws = $connector('ws://0.0.0.0:5069', [], ['Origin' => 'http://localhost']);
->then(function(Ratchet\Client\WebSocket $conn) use ( $loop ) {
$loop->addPeriodicTimer(1, function () use ( $conn ) {
$conn->send('Figured out?');
});
// Simple echo on message received for the test.
$conn->on('message', function(\Ratchet\RFC6455\Messaging\MessageInterface $msg) use ($conn) {
echo "Received: {$msg}\n";
});
$conn->on('close', function($code = null, $reason = null) {
echo "Connection closed ({$code} - {$reason})\n";
});
$conn->send('Hello World!');
}, function(\Exception $e) use ($loop) {
echo "Could not connect: {$e->getMessage()}\n";
$loop->stop();
});
$loop->run();
I'm using rachetphp to create a client for an api server.
But i have a problem, when my connection close, whatever the reason, i can't reconnect automatically.
here the lib i use : https://github.com/ratchetphp/Pawl
<?php
require __DIR__ . '/vendor/autoload.php';
$loop = React\EventLoop\Factory::create();
$connector = new Ratchet\Client\Connector($loop);
$connector('ws://127.0.0.1:9000', ['protocol1', 'subprotocol2'], ['Origin' => 'http://localhost'])
->then(function(Ratchet\Client\WebSocket $conn) {
$conn->on('message', function(\Ratchet\RFC6455\Messaging\MessageInterface $msg) use ($conn) {
echo "Received: {$msg}\n";
$conn->close();
});
$conn->on('close', function($code = null, $reason = null) {
echo "Connection closed ({$code} - {$reason})\n";
});
$conn->send('Hello World!');
}, function(\Exception $e) use ($loop) {
echo "Could not connect: {$e->getMessage()}\n";
$loop->stop();
});
$loop->run();
I would like to try a reconnect every Seconds after a connection close.
Any ideas?
The idea is simple but it needs some refactoring. We must put the reconnect code in the handler that is executed when the connection is closed. In order to do that we pass the $app function inside self.
require __DIR__ . '/vendor/autoload.php';
$loop = React\EventLoop\Factory::create();
$connector = new Ratchet\Client\Connector($loop);
$app = function (Ratchet\Client\WebSocket $conn) use ($connector, $loop, &$app) {
$conn->on('message', function (\Ratchet\RFC6455\Messaging\MessageInterface $msg) use ($conn) {
echo "Received: {$msg}\n";
$conn->close();
});
$conn->on('close', function ($code = null, $reason = null) use ($connector, $loop, $app) {
echo "Connection closed ({$code} - {$reason})\n";
//in 3 seconds the app will reconnect
$loop->addTimer(3, function () use ($connector, $loop, $app) {
connectToServer($connector, $loop, $app);
});
});
$conn->send('Hello World!');
};
function connectToServer($connector, $loop, $app)
{
$connector('ws://127.0.0.1:9000', ['protocol1', 'subprotocol2'], ['Origin' => 'http://localhost'])
->then($app, function (\Exception $e) use ($loop) {
echo "Could not connect: {$e->getMessage()}\n";
$loop->stop();
});
}
connectToServer($connector, $loop, $app);
$loop->run();
The idea is that when the connection receives the close event we do a reconnect using the connectToServer function:
$conn->on('close', function ($code = null, $reason = null) use ($connector, $loop, $app) {
echo "Connection closed ({$code} - {$reason})\n";
//in 3 seconds the app will reconnect
$loop->addTimer(3, function () use ($connector, $loop, $app) {
connectToServer($connector, $loop, $app);
});
});
If you have pnctl (http://php.net/manual/en/book.pcntl.php) installed, you can just change the 'close' handler to this:
$conn->on('close', function($code = null, $reason = null) {
echo "Connection closed ({$code} - {$reason})\n";
// restart myself
global $argv;
pcntl_exec($_SERVER['_'], $argv);
exit();
});
For me this was simpler because I didn't already have the $loop and $connector extracted, I am just using
\Ratchet\Client\connect($ws_url, [], $headers)
->then(function ($conn) {
echo "connected\n";
...
To handle it in multiple places, I created a little helper function that I call on those events (based on the case, I add a delay).
function restartMyself() {
global $argv;
pcntl_exec($_SERVER['_'], $argv);
exit();
}
The following has an on error event. How can I determine the specific error?
<?php
$loop = Factory::create();
$socket = new React\Socket\Server($loop);
$socket->on('connection', function (\React\Socket\ConnectionInterface $stream){
$stream->on('data', function($rsp) {
echo('on data');
});
$stream->on('close', function($conn) {
echo('on close');
});
$stream->on('error', function($conn) use ($stream) {
echo('on error');
// How do I determine the specific error?
$stream->close();
});
echo("on connect");
});
$socket->listen('0.0.0.0',1337);
$loop->run();
Looking at the implementation of a ConnectionInterface, React\Socket\Connection, it extends React\Stream\Stream, which uses emit() (which will trigger the callbacks registered with on):
https://github.com/reactphp/stream/blob/c3647ea3d338ebc7332b1a29959f305e62cf2136/src/Stream.php#L61
$that = $this;
$this->buffer->on('error', function ($error) use ($that) {
$that->emit('error', array($error, $that));
$that->close();
});
Thus, the first argument of that function is the error, the second one the $stream:
$stream->on('error', function($error, $stream) {
echo "an exception happened: $error";
// $error will be an instance of Throwable then
$stream->close();
});
Making requests from angular http to local Apache 2.4 server running php restapi with slim framework. the requests take 15+ seconds to come back. however when I use postman to test the api the responses take 20ms which is what I expect. I'm brand new to php, is there some configuration I'm missing?
<?php
require 'vendor/autoload.php';
$app = new Slim\Slim();
// ==============================
// Connection ===================
// ==============================
function connect()
{
$servername = "localhost";
$username = "root";
$password = "******";
try {
$conn = new PDO("mysql:host=$servername;dbname=contacts", $username, $password);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
//echo "Connected successfully<br>";
} catch (PDOException $e) {
echo "Connection failed: " . $e->getMessage();
}
return $conn;
}
$app->get('/contacts', function () use ($app) {
getContacts(connect(), $app);
});
$app->run();
function getContacts($conn, $app)
{
$app->response()->header("Content-Type", "application/json");
$app->response()->header('Access-Control-Allow-Origin', '*');
$app->response()->header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
$app->response()->header('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
$app->response()->header('Access-Control-Allow-Credentials', true);
try {
$stmt = $conn->prepare("SELECT * FROM contacts");
$stmt->execute();
echo json_encode($stmt->fetchAll());
} catch (PDOException $e) {
echo $e->getMessage();
}
$conn = null;
}
The Request
$http({
method: 'GET',
url : "http://localhost/Contacts_PHP/contactsAPI.php/contacts"
}).success(function (data) {
$scope.contacts = data;
console.log($scope.contacts);
}).error(function (ex) {
console.log(ex);
});
Try closing the connection by sending the following header:
$app->response->headers->set('Connection', 'close');
But first, set your response body by using the setBody() method, instead of using echo:
$app->response->setBody(json_encode($data));