Route requests to Laravel Controller to running WebSocket server - php

The situation:
There is a long-running task that needs to be launched asynchronously. The details of this task aren't really all that important (the basics are that multiple VMs are going to be provisioned and a complex network setup), and this is being handled by a python script that will be running on a different server. We decided to go with WebSockets for the communication back and forth between the web server and the client, and I have the bi-directional communication there working. The web server will be sending requests to the other server, and will receive HTTP POST notifications back from the python script when each machine is up and running, and a final HTTP POST back when the entire network is up.
All of this works. The framework we're using on the web server is Laravel 4, and the web socket server was built using Ratchet, and implemented in an artisan command. My issue is I'm not sure how to relay the HTTP POSTs to a Laravel controller from the python script to the WebSocket server so that it in turn can relay that information back to the client.
Below is the implementation of Ratchet's MessageComponentInterface::onMessage method, which is using an Observer pattern to notify any subscribed listeners of a message event.
// Class: SocketMessenger
public function onMessage(ConnectionInterface $from, $msg) {
$respondTo = null;
foreach($this->_clients as $client) {
if($client->resourceId == $from->resourceId) {
$respondTo = $client;
break;
}
}
$msg = json_decode($msg, true);
if(!$respondTo || !$msg || !isset($msg['type'])) {
echo 'Malformed Socket Message Received, Rejecting.';
return;
}
$type = $msg['type'];
// Notify any subscribed listeners with this message type of the
// incoming message. Any response received will be relayed back to
// the client.
if(isset($this->_listeners[$type])) {
foreach( $this->_listeners[$type] as $listener ) {
$response = $listener->notify( $respondTo, $msg );
if($response) {
$respondTo->send( $response );
}
}
}
}
The WebSocket server is constructed within a service provider like this:
// Class: SocketServiceProvider
$app->bind( 'Ratchet\MessageComponentInterface', function( $app, $params ) {
$port = $params && isset($params[0]) ?
$params[0] : self::DEFAULT_PORT
;
$messenger = new SocketMessenger;
// Setup the Ratchet Server.
$server = IoServer::factory(
new HttpServer(
new WsServer(
$messenger
)
),
$port
);
// Subscribe socket listeners to the socket messenger
$messenger->subscribe( new TopologyMessageListener, TopologyMessageListener::TYPE );
return $server;
} );
Is there any way take requests to a Laravel controller and notify the running WebSocket server whenever a given controller's method is hit?

The answer here appears to be that I need to create a WebSocket client in PHP that will send messages to the WebSocket server, the same as a client written in JavaScript would. For any interested viewers struggling with the same issue, I was able to find a few WebSocket Clients written in PHP:
https://github.com/gabrielbull/php-websocket-client
https://github.com/Devristo/phpws
https://github.com/symbiose/php-websocket-client (<-- This is the one I wound up going with)
I'll be leaving this question open for awhile in case anyone comes up with a different idea / better client library to use.

Related

Ratchet PHP with ZMQ and OriginCheck - am I doing it wrong?

Please consider the following Ratchet PHP WS server creation. It listens for WS client connections as well as messages from ZMQ. Upon reception of ZMQ messages, it forwards the info to some client WS connections based on business logic behind.
I only use an insecure websocket here as I use Apache to reverse-proxy for SSL.
The problem is that it works perfectly when accessed from a browser, but when accessed by a command line WS client, every once in a while I get the following warning, at which point the socket closes:
It didn't happen until I added the Origin checking mechanism. Should I stick to Ratchet's origin checking, or use some other mechanism?
My server creation code (is it even correct?):
// React loop
$loop = React\EventLoop\Factory::create();
$pusher = new MyApp\Api;
// ZMQ
$context = new React\ZMQ\Context($loop);
$pull = $context->getSocket(ZMQ::SOCKET_PULL);
$pull->bind("tcp://127.0.0.1:7777");
$pull->on('message', array($pusher, 'onZMQ'));
// WS
$webSock_insecure = new React\Socket\Server(
"127.0.0.1:8080",
$loop
);
$wsSrv = new Ratchet\WebSocket\WsServer($pusher);
$wsSrv->enableKeepAlive($loop, 1);
$checkedWsSrv = new Ratchet\Http\OriginCheck($wsSrv, ['localhost']);
$checkedWsSrv->allowedOrigins[] = '';
$checkedWsSrv->allowedOrigins[] = 'mysite.com';
$webServer_insecure = new Ratchet\Server\IoServer(
new Ratchet\Http\HttpServer($checkedWsSrv),
$webSock_insecure
);
$loop->run();

Amazon SQS service with Elastic Beanstalk

I am fairly new to Amazon SQS and I am having a hard time understanding what is going on. I set the HTTP Path to a file that could handle the requests. but I am not sure if that was the correct thing to do. What is the proper way? Why do the messages go directly to "In Flight"? What happens when the message is sent to the HTTP path? I am using php for my application, so if someone could give me guidance on what I am doing wrong, then I would greatly appreciate it!
When I check for a message with my php script that looks likes this:
$sqs_client = new SqsClient($sqs_credentials);
// Get the queue URL from the queue name.
$result = $sqs_client->getQueueUrl(array('QueueName' => "NormalPoll"));
$queue_url = $result->get('QueueUrl');
// Receive a message from the queue
$result = $sqs_client->receiveMessage(array(
'QueueUrl' => $queue_url
));
if ($result['Messages'] == null) {
die('No Message');
}
// Get the message information
$result_message = array_pop($result['Messages']);
$body = $result_message['Body'];
print $body;
I always get 'No Message' in return when running the program. I inserted a message via the AWS SQS console and I am unable to receive it. The message goes automatically to 'in flight'.
Thanks In Advance!
The messages are inflight because of the beanstalk worker environment configuration.
The following documentation provides details on how to configure a beanstalk worker environment.
http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features-managing-env-tiers.html

Guidance on using openstack to launch an instance via php and automatically build an instance depending on request?

A very open question which I need some advice on and more importantly pointers in the right direction.
I'm looking at using openstack for my private cloud (currently using VMware) as the main aim is to be able to launch a new VM instance from within our web application so this could be trigger via a php page to deploy new apache worker server for example. The next aim is to develop our code to be able to see when a server load is getting high or needs more worker servers to preform a task to auto launch an instance?
I've been looking at the openstack API to see if this is the best approach? But also looking at juju to see if you can use charms to do this and seeing if the api for juju to is best?
The aim is get this working with VMware or to replace vmware.
My current setup is running openstack on a laptop using nova as the storage so any help with the pointers would be great
I know its a open question
Well there is an SDK page listing many of the OpenStack API client SDKs that exist.
Ref:
https://wiki.openstack.org/wiki/SDKs#PHP
Listed in there are two PHP SDKs for OpenStack currently:
Ref:
https://github.com/rackspace/php-opencloud
https://github.com/zendframework/ZendService_OpenStack
I wouldn't use Juju as an interface. And frankly I am not sure OpenStack is the right tool for what you are doing. But, if you want to play with devstack and get an idea, I think rackspace's php client SDK is probably a good start. Devstack is not a bad way to get that experience either.
example of spinning up a server with php-opencloud:
$server = $compute->server();
try {
$response = $server->create(array(
'name' => 'My lovely server',
'image' => $ubuntu,
'flavor' => $twoGbFlavor
));
} catch (\Guzzle\Http\Exception\BadResponseException $e) {
// No! Something failed. Let's find out:
$responseBody = (string) $e->getResponse()->getBody();
$statusCode = $e->getResponse()->getStatusCode();
$headers = $e->getResponse()->getHeaderLines();
echo sprintf("Status: %s\nBody: %s\nHeaders: %s", $statusCode, $responseBody, implode(', ', $headers));
}
This would be a polling function:
use OpenCloud\Compute\Constants\ServerState;
$callback = function($server) {
if (!empty($server->error)) {
var_dump($server->error);
exit;
} else {
echo sprintf(
"Waiting on %s/%-12s %4s%%",
$server->name(),
$server->status(),
isset($server->progress) ? $server->progress : 0
);
}
};
$server->waitFor(ServerState::ACTIVE, 600, $callback);

SMPP connection

I'm establishing an SMPP connection via PHP using this free library. To receive a message, I'm using the following code, given in the example:
<?php
$GLOBALS['SMPP_ROOT'] = dirname(__FILE__); // assumes this file is in the root
require_once $GLOBALS['SMPP_ROOT'].'/protocol/smppclient.class.php';
require_once $GLOBALS['SMPP_ROOT'].'/transport/tsocket.class.php';
// Construct transport and client
$transport = new TSocket('your.smsc.com',2775);
$transport->setRecvTimeout(60000); // for this example wait up to 60 seconds for data
$smpp = new SmppClient($transport);
// Activate binary hex-output of server interaction
$smpp->debug = true;
// Open the connection
$transport->open();
$smpp->bindReceiver("USERNAME","PASSWORD");
// Read SMS and output
$sms = $smpp->readSMS();
echo "SMS:\n";
var_dump($sms);
// Close connection
$smpp->close();
?>
It works perfectly well, when I run the script in the browser window and send the SMS from my phone within given 60 seconds, but I don't quite understand how to make it work for a long time. I mean, like in real-life situation, when it should run on the background and trigger some events when receiving an SMS. How do I do that? Because now, I need to refresh the page every time to get an SMS, and it only works once. Thanks in advance.
If your solution needs to run within a browser, you shouldn't connect to the SMPP server directly from your script. It would lead to a single user scenario.
You should put a endless loop around the readSMS call and make it a console application which runs as a daemon. Then you write the result of readSMS into a database and read this from your web application. With this you could use html refresh or some fancy ajax querying the database and presenting the incoming sms.
Usually SMPP receiver connections run in blocking mode on the socket (no timeout), because either you receive either a SMS, or an enquire_link (which needs to be answered by a enquire_link_resp - your library does this automatically). Whenever you read a SMS, process it (put it in the database) and call readSMS again - it will block until the next SMS comes in.
You can try this.
<?php
set_time_limit(0);
$GLOBALS['SMPP_ROOT'] = dirname(__FILE__); // assumes this file is in the root
require_once $GLOBALS['SMPP_ROOT'].'/protocol/smppclient.class.php';
require_once $GLOBALS['SMPP_ROOT'].'/transport/tsocket.class.php';
// Construct transport and client
$transport = new TSocket('your.smsc.com',2775);
$transport->setRecvTimeout(60000); // for this example wait up to 60 seconds for data
$smpp = new SmppClient($transport);
// Activate binary hex-output of server interaction
$smpp->debug = true;
// Open the connection
$transport->open();
$smpp->bindReceiver("USERNAME","PASSWORD");
while(1) {
// Read SMS and output
$sms = $smpp->readSMS();
echo "SMS:\n";
var_dump($sms);
}
// Close connection
$smpp->close();
?>
Try to use another library
composer require glushkovds/php-smpp
To receive sms:
<?php
require_once 'vendor/autoload.php';
$service = new \PhpSmpp\Service\Listener(['your.smsc.com'], 'login', 'pass');
$service->listen(function (\PhpSmpp\SMPP\Unit\Sm $sm) {
if ($sm instanceof \PhpSmpp\Pdu\DeliverSm) {
var_dump($sm->message);
}
});

PHP - echo do other client

I`m making an little php server that has 2 clients, on connects to the php (device a) and controls the other (device b).
device a makes a get request to the php. now i want to push that command to device b. is there any way to echo to device b? or to make an request to the device b?
(i only need to send one character to device b)
Pushing to device is possible but depends on your device.
A solution would be websockets, see the following links for further reading:
http://www.websocket.org/
http://code.google.com/p/phpwebsocket/
Another solution would be longpolling which is easy to implement in php:
http://en.wikipedia.org/wiki/Push_technology#Long_polling
Very simple implementation of longpolling on server-side:
$ts = time();
while(true) {
// if there's something new, send the response to the server
// if not, continue with the loop
if($response = getSuperAwesomeResponse()) {
print $response;
break;
}
// timeout after 60 seconds
if( ($ts + 60) > time()) {
break;
}
sleep(1);
}
On the client-side you just need to send some sort of ajax calls
No, unless device B is running a server of some kind (any software that accepts incoming connections really). If that's the case then you can easily make HTTP requests to the device (e.g. even with file_get_contents) or have your own custom connection protocol (with sockets). There are also other options that allow you the same functionality but work in slightly different ways.
If the device is not running any servers then the best you can do is poll the server continuously to see if there are any commands for it. This is easier to set up (the server is already there) but also not efficient.
Device B could open a client connection to the server and wait for incomming data. If data comes in, the client running on device B could echo it.
PHP offers access to network sockets, see http://www.php.net/manual/en/book.sockets.php
Some PHP example code making use of LibEvent and ZMQ, which allows a higher level of access to sockets and queues:
Event-driven Server:
<?php
// create base and event
$base = event_base_new();
$event = event_new();
// Allocate a new context
$context = new ZMQContext();
// Create sockets
$rep = $context->getSocket(ZMQ::SOCKET_REP);
// Connect the socket
$rep->bind("tcp://127.0.0.1:5555");
// Get the stream descriptor
$fd = $rep->getsockopt(ZMQ::SOCKOPT_FD);
// Define event callback function
$fnc = function ($fd, $events, $arg) {
static $msgs = 1;
echo "CALLBACK FIRED" . PHP_EOL;
if($arg[0]->getsockopt (ZMQ::SOCKOPT_EVENTS) & ZMQ::POLL_IN) {
echo "Got incoming data" . PHP_EOL;
var_dump ($arg[0]->recv());
$arg[0]->send("Got msg $msgs");
if($msgs++ >= 10) event_base_loopexit($arg[1]);
}
};
// set event flags
event_set($event, $fd, EV_READ | EV_PERSIST, $fnc, array($rep, $base));
// set event base
event_base_set($event, $base);
// enable event
event_add($event);
// start event loop
event_base_loop($base);
ZeroMQ Client:
<?php
// Create new queue object
$queue = new ZMQSocket(new ZMQContext(), ZMQ::SOCKET_REQ, "MySock1");
$queue->connect("tcp://127.0.0.1:5555");
// Assign socket 1 to the queue, send and receive
var_dump($queue->send("hello there!")->recv());
Source, Talk, Video (~22:00).

Categories