Why ActiveMQ delivers duplicate messages to my PHP consumer over Stomp? - php

I am not sure whether this question is related to stomp-php or ActiveMQ Docker (running with defaults).
I have a simple Queue helper class written in PHP that handles both sending the message to the queue (Queue::push), as well as consumes it (Queue::fetch). See code below.
As you can see, fetch() should subscribe to the queue, read one message and unsubscribe. The message should be acknowledged automatically (\Stomp\StatefulStomp::subscribe(), 3rd. argument).
For some reason, about 5-7% of the messages are received by the customer twice or even three times. Why messages are delivered multiple times and how to avoid it?
Publisher (pushing 1000 messages):
$mq = new Queue('tcp://activemq:61613','test');
for ($msgCount = 0; $msgCount < 1000; $msgCount++) {
$mq->push('Message #' . $msgCount);
}
Consumer (receiving ~1070 messages):
$mq = new Queue('tcp://activemq:61613','test');
$received = 0;
while (true) {
$message = $mq->fetch();
if (null === $message) { break; }
$received++;
}
Queue class code:
use Stomp\Client;
use Stomp\Network\Connection;
use Stomp\SimpleStomp;
use Stomp\StatefulStomp;
use Stomp\Transport\Message;
class Queue
{
/**
* #var \Stomp\StatefulStomp
*/
private $stomp;
private $queue;
public function __construct($uri, $queue) {
$connection = new Connection('tcp://activemq:61613');
$this->stomp = new StatefulStomp(new Client($connection));
$connection->setReadTimeout(1);
$this->queue = $queue;
}
public function push($body) {
$message = new Message($body, ['activemq.maximumRedeliveries' => 0]);
$this->stomp->send('/queue/' . $this->queue, $message);
}
public function fetch() {
$subscriptionId = $this->stomp->subscribe('/queue/' . $this->queue, null, 'auto', ['activemq.prefetchSize' => 1]);
$msg = $this->stomp->read();
$this->stomp->unsubscribe($subscriptionId);
return $msg;
}
}

Related

Send event from php controller

Good morning everyone.
I need to send a notification whenever a user swipes a tab on a particular sensor.
The problem is not the connection to the sensor, which at this moment already takes place and subject to user access.
Currently I have created a server socket inside my yii2 app to be able to send the notification event to the client and update them in real time.
This is my controller server
class ServerController extends Controller
{
public function actionStart()
{
// $server = new CommandsServer();
$server = new ChatServer();
$server->port = 80; //This port must be busy by WebServer and we handle an error
$server->on(WebSocketServer::EVENT_WEBSOCKET_OPEN_ERROR, function ($e) use ($server) {
echo "Error opening port " . $server->port . "\n";
$server->port += 1; //Try next port to open
$server->start();
});
$server->on(WebSocketServer::EVENT_WEBSOCKET_OPEN, function ($e) use ($server) {
echo "Server started at port " . $server->port;
});
$server->start();
}
}
This is my chat server which I created for testing
<?php
namespace frontend\daemons;
use consik\yii2websocket\events\WSClientEvent;
use consik\yii2websocket\WebSocketServer;
use Ratchet\ConnectionInterface;
class ChatServer extends WebSocketServer
{
public function init()
{
parent::init();
$this->on(self::EVENT_CLIENT_CONNECTED, function (WSClientEvent $e) {
$e->client->name = null;
});
}
protected function getCommand(ConnectionInterface $from, $msg)
{
$request = json_decode($msg, true);
return !empty($request['action']) ? $request['action'] : parent::getCommand($from, $msg);
}
public function commandPing(ConnectionInterface $client, $msg)
{
$arr = ["Neo", "Morpheus", "Trinity", "Cypher", "Tank"];
$res = ['type' => 'ping', 'message' => json_encode(array_rand($arr, 1))];
foreach ($this->clients as $chatClient) {
$chatClient->send(json_encode($res));
}
}
}
Now, what I wish I could do is be able to use that commandPing inside another controller, but I haven't found a way I can implement this.
In this sense the round would be:
user swipes card on sensor -> the sensor calls my method to see if the user is actually authorized to enter -> I call commandPing (as an example) to send a notification to the customer (OK / KO )
On the web interface side I will then intercept the message via new Websocket (but this is not a problem)

How to properly unpack Protobuf Any type in PHP

I'm desperately trying to check whether an "unknown" protobuf payload wrapped within an Any is some specific type (Heartbeat) and unpack it to a Heartbeat object using the code below. Both Heartbeat and StatusChanged are valid generated Protobuf classes.
/* <external service> */
$event = (new StatusChanged)->setId("testId");
$any = new Any;
$any->pack($event);
$protobufBinaryAny = $any->serializeToString();
/* </external service> */
/* $protobufBinaryAny is the incoming binary data */
$anyEvent = new Any();
$anyEvent->mergeFromString($protobufBinaryAny);
if ($anyEvent->is(Heartbeat::class)) {
$this->processHeartbeat($anyEvent->unpack());
} else {
throw new UnknownMessageTypeException($anyEvent->getTypeUrl());
}
When running the following code, I'd expect it to throw a UnknownMessageTypeException, however, I get this:
Call to a member function getFullName() on null.
This error happens at $data->is(Heartbeat::class), since the Heartbeat class obviously couldn't be found in the DescriptorPool.
/**
* Google\Protobuf\Any
* https://github.com/protocolbuffers/protobuf/blob/440a156e1cd5c783d8d64eef81a93b1df3d78b60/php/src/Google/Protobuf/Any.php#L315-L323
*/
class Any {
public function is($klass)
{
$pool = \Google\Protobuf\Internal\DescriptorPool::getGeneratedPool();
$desc = $pool->getDescriptorByClassName($klass);
$fully_qualifed_name = $desc->getFullName();
$type_url = GPBUtil::TYPE_URL_PREFIX.substr(
$fully_qualifed_name, 1, strlen($fully_qualifed_name));
return $this->type_url === $type_url;
}
}
However, when explicitly initializing the Heartbeat before, it's working, since Heartbeat is now in the DescriptorPool.
foreach ([
\GPBMetadata\Heartbeat::class,
\GPBMetadata\StatusChanged::class,
] as $klass) {
$klass::initOnce();
}
But this manual initialization thing just feels wrong. Am I supposed to do it like that, or did I just miss a fancy autoloading-feature in the non-existing PHP docs?
For the time being, I wrote an autoloader for this case, but it still just feels not right:
trait HandlesProtobufMessages
{
function initializeProtobufMessages()
{
/** #var \Composer\Autoload\ClassLoader */
$loader = require(__DIR__.'/../../vendor/autoload.php');
$classes = array_filter(array_keys($loader->getClassMap()), function ($className) {
$namespace = "GPBMetadata\\";
return substr($className, 0, strlen($namespace)) === $namespace;
});
foreach ($classes as $class) {
$class::initOnce();
}
}
}
Maybe you have more insights into this protobuf jungle and can help me out here, would be glad!
Thank you in advance

Dynamically-created Twilio Enqueue waitUrl results in 500 server error

I have a function in my Laravel application that generates TwiML for a holding queue. It seems that when I try to dynamically generate the value for the waitUrl attribute, I end up getting a 500 server error during runtime. Routes are properly established and I'm able to view the correct XML at the waitURL in the browser. However, the error persists.
If I create a static XML file with the same exact content, or use a TwiML Bin, it works like a charm.
Here are the relevant functions:
public function wait() {
return $this->generateWaitTwiml();
}
public function onHold($agentId) {
return $this->generateHoldQueueTwiml($agentId, '/phone/wait');
}
private function generateHoldQueueTwiml($agentId, $waitUrl = null) {
$queue = $agentId . '_hold';
if ($waitUrl === null){
$waitUrl = 'path_to_static.xml';
}
$queue = $agentId . '_hold';
$response = new Twiml();
$response->enqueue(
$queue,
['waitUrl' => $waitUrl]
);
return response($response)->header('Content-Type', 'application/xml');
}
private function generateWaitTwiml() {
$response = new Twiml();
$response
->play('http://path_to_my.mp3');
return response($response)->header('Content-Type', 'application/xml');
}
This was resolved by excluding the URIs from the CSRF verification (in VerifyCsrfToken.php):
class VerifyCsrfToken extends Middleware {
protected $except = [
'uri/',
'uri2/*',
];
}

Laravel Queue - remembering property state?

If a job failed, it will be pushed back to the Queue. Is there a way to remember the value of property in the job class when processing job again?
For example:
class MailJob extends Job
{
public $tries = 3;
public $status;
public function __construct()
{
$this->status = false; // set to false
}
/**
* Execute the job.
*/
public function handle()
{
$this->status = true;
// Assume job has failed, it went back to the Queue.
// status should be true when this job start processing again
}
}
If you want to rerun the failed process again on the same moment. you can do something like this.
Here the object is in memory while rerunning the job so data will be available.
I haven't verified it by running it , but hope it will work
class MailJob extends Job{
public $tries = 3;
public $status;
public function __construct()
{
$this->status = false; // set to false
}
/**
* Execute the job.
*/
public function handle()
{
$this->status = true;
// Assume job has failed, it went back to the Queue.
// status should be true when this job start processing again
$failed = processFailedisConfirm();
if $failed == true && $this->tries > -1 {
$this->tries = $this->tries - 1;
$this->handel();
}
}}
Example of processFailedisConfirm could be
public function processFailedisConfirm(){
// Core Process to be done in the Job
$state = (Do Some Api Call); // Here just example, you may send email
// Or can do the core Job Process
// And depending on the Response of the
// Process return true or false
// Is Job failed or not ?
if ( $state == "200" ){
return false; // Job is not failed
} else {
return true; // Job is failed
}
Logic of process is failed or not is depened on the operation you are doing. As i am doing an api call if i get response of 200 my process is successfull.
Otherwise process is failed.
This just example, sucess reponse of different api can be differnt as desigend by api designer.

Alternative for handling requests using switch statement

I have to set up multiple cron jobs. Each cron will be a separate request to the server. So, I started by the following, where each request will be handled by a case inside the switch, but the cases are bound to increase and thus doesn't seem to me a very good idea.
require_once './invoice_cron.php';
$checkRequest = isset($_REQUEST['request']);
if($checkRequest) {
$request_name = $_REQUEST['request'];
switch($request_name) {
case 'send_invoice':
break;
default:
break;
}
}
What could be a better approach here?
Let there be a request handler interface:
<?php
// CronRequests.php
require_once __DIR__.'./autoload.php';
$request_name = isset($_REQUEST['request']) ?
(new _cron)->handler($_REQUEST['request']) :
null ;
Make a class that could handle each request:
class _cron {
/**
* List of possible requests
* #var array
*/
private static $REQUESTS = ['send_invoice','start_user_subscription'];
/**
* HTTP request handler for all cron jobs
* #param string $request_name Name of the request
*/
public function handler($request_name) {
$status = false;
if(isset($request_name)) {
$request_map = array_flip(self::$REQUESTS);
if(isset($request_map[$request_name])) {
$status = $this->$request_name();
}
}
return $status;
}
}
The list of requests are bound to increase, so it is necessary to search the list efficiently. So, here we do an array flip and check a key for existence.
for each request type must match a function
all the types of requests must be defined in the system before start the processing
if a request is not in the list => some logical response must happen
i will give you just simple example, you can develop it
class Request{
private $REQUEST_TYPES = ['get_invoice', 'set_invoice', 'print_invoice'];
public function handler($requestKey){
foreach($this->REQUEST_TYPES as $type){
if($requestKey == $type) $this->$type();
}
$this->handlerNotFound();
}
private function get_invoice(){
//do some thing here
}
private function set_invoice(){
//do some thing here
}
private function print_invoice(){
//do some thing here
}
private function handlerNotFound(){
//do some thing here
}
}

Categories