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();
});
Related
I use workerman/workerman for websockets.
The main question – how to send messages on websocket closing?
Also I tried to send messages to other connections. No result too.
public function run()
{
$this->ws = new Worker(
'websocket://0.0.0.0:' . $_ENV['WS_PORT'],
['ssl' => [
'local_cert' => $_ENV['WS_CERTIFICATE_CRT'],
'local_pk' => $_ENV['WS_CERTIFICATE_KEY'],
'verify_peer' => false,
]]
);
$this->ws->count = 1;
$this->ws->transport = 'ssl';
$this->ws->onConnect = function ($connection) {
$this->onConnect($connection);
};
$this->ws->onMessage = function ($connection, $data) {
$this->onMessage($connection, $data);
};
$this->ws->onClose = function ($connection) {
$this->onClose($connection);
};
Worker::runAll();
}
This doesn't work
private function onClose($connection)
{
$connection->send("Hello");
}
Looking at the source code of workerman/workerman we can see that the onClose event is implemented as follows:
// Close socket.
try {
#fclose($this->socket);
} catch (Throwable) {
}
$this->status = self::STATUS_CLOSED;
// Try to emit onClose callback.
if ($this->onClose) {
try {
($this->onClose)($this);
} catch (Throwable $e) {
$this->error($e);
}
}
As you can see, onClose is emitted AFTER the socket has been closed.
So you will not be able to use that socket in your onClose handler anymore.
A better solution would be that the client sends some sort of "close" packet to the server.
The server can then run some logic and close the connection after it is done.
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();
So Im building an API.
But I keep running into returning a json response from an inner function.
If I do the following then laravel sends a backlog or error log to the client. That should never ever happen in a api. So how do you do this in Laravel?
I want to return json immediately and stop excecuting without showing the client any other information
public function functionThatIsCalledByClient($data)
{
$this->validateSomething($data);
}
private function validateSomething($data)
{
if(! $data ) ) return response()->json(['error' => 'some message'], 400)->send();
return true;
}
You could use abort helper for that or, for complex cases, error handling.
In case of abort:
private function validateSomething($data)
{
if(! $data ) // you can create a custom helper function to wrap this code.
abort(400, json_encode(['error'=>'some error']), ['Content-Type: application/json']);
return true;
}
In case of general handler:
private function validateSomething($data)
{
if(! $data )
throw new \Exception('some message');
return true;
}
Inside app/Exceptions/Handler.php # render
public function render($request, Exception $e)
{
if($e instanceof Exception)
return response()
->json(['error' => $e->getMessage()], 400);
//->send(); should not be necessary
}
throw new GeneralException('invalid required extra quantity');
I followed the Slim v3 documentation to a tee on how to redefine the framework's built in error handler so as to prevent errors from being outputted to the response with the following code:
$settings = require __DIR__ . '/../src/settings.php';
$app = new \Slim\App($settings);
// Errors to log rather than to end user
$c = $app->getContainer();
$c['errorHandler'] = function ($c) {
return function ($request, $response, $exception) use ($c) {
$c['logger']->error("Error 500 diverted: ".$exception->getMessage());
return $c['response']->withStatus(500)
->withHeader('Content-Type', 'text/html')
->write('Something went wrong on our side!');
};
};
And yet my API is still spitting out Slim's default handler with full stack traces and the telltale Slim Application Error string... albeit helpful, I'd much rather have this information going to Slim's log (monolog) and something less revealing facing the client. Is there any reason that this service redefinition is effectively being ignored?
This code works:
<?php
$app = new \Slim\App();
$container = $app->getContainer();
$container['errorHandler'] = function ($c) {
return function ($request, $response, $exception) use ($c) {
// log error here
return $c['response']->withStatus(500)
->withHeader('Content-Type', 'text/html')
->write('Something went wrong on our side!');
};
};
$container['phpErrorHandler'] = function ($c) {
return function ($request, $response, $error) use ($c) {
// log error here
return $c['response']->withStatus(500)
->withHeader('Content-Type', 'text/html')
->write('Something went wrong on our side (again)!');
};
};
$app->get('/exception', function ($req, $res, $args) {
// errorHandler will trap this Exception
throw new Exception("An error happened here");
});
$app->get('/php7', function ($req, $res, $args) {
$x = function (int $x) {
return $x;
};
// phpErrorHandler wil trap this Error
$x('test');
});
$app->get('/warning', function ($req, $res, $args) {
$x = UNDEFINED_CONSTANT;
});
$app->run();
As you can see, you need to register an errorHandler to catch Exceptions and a phpErrorHandler to catch PHP 7 Errors.
Note that neither will catch a PHP Notice as shown by the /warning route. To catch that too, you need to register your own error handler:
set_error_handler(function ($severity, $message, $file, $line) {
if (!(error_reporting() & $severity)) {
// This error code is not included in error_reporting, so ignore it
return;
}
throw new \ErrorException($message, 0, $severity, $file, $line);
});
I have a function
<?php
public function setStatusToReadyToShip(array $order_item_ids, $delivery_type, $shipping_provider = '', $tracking_number = '')
{
//code
}
Now I want the caller to call this method by providing $order_item_ids in array format.
One way i could do is to check is_array($order_item_ids) and send the error to the caller. But i am to utilize Type Hinting. How would i send the user the error response when using type hinting. As currently it crashes the application and says
exception 'ErrorException' with message 'Argument 1 passed to Class::SetStatusToReadyToShip() must be of the type array, integer given
I am not familiar with the try catch stuff that much. I tried placing try-catch inside this function so that it does not crash the application but the same output received.
Thanks
It is Catchable fatal error with constant E_RECOVERABLE_ERROR
There are 2 methods to solve this problem (at least two).
Create your own exception handler and set it as main error handler.
Make type validation in your function, and throw exception from it:
function setStatusToReadyToShip($order_item_ids, $delivery_type, $shipping_provider = '', $tracking_number = '')
{
if (!is_array($order_item_ids)) {
throw new InvalidArgumentException('First parameter of function '.__FUNCTION__.' must be array');
}
// your code
}
and than catch it
try {
setStatusToReadyToShip(5, 6);
} catch(InvalidArgumentException $e) {
var_dump($e->getMessage());
}
<?php
class YourClass {
public static function _init() {
set_error_handler('YourClass::_typeHintHandler');
}
public static function _typeHintHandler($errorCode, $errorMessage) {
throw new InvalidArgumentException($errorMessage);
}
public function setStatusToReadyToShip(array $order_item_ids, $delivery_type, $shipping_provider = '', $tracking_number = '')
{
}
}
YourClass::_init();
$yo = new YourClass();
try {
$yo->setStatusToReadyToShip("test");
}catch(Exception $e) {
print $e->getMessage();
}