Search code examples
javascriptphpnode.jssocket.ioratchet

Read long-running PHP process client side


I want to read a long-running php process that will return data when a criterion is met.

From my research, I have came across:

  • Long polling
  • Sockets (socket.io & node.js)
  • Ratchet

I am struggling to understand & apply an implementation of my problem. I have the following loop in PHP:

public function store(ClientImpl $a)
{
    $request = \Illuminate\Support\Facades\Request::all();
    $originateMsg = new OriginateAction('Local/' . $request['agent'] . '@auto-answer');
    $originateMsg->setContext('G-Outgoing');
    $originateMsg->setPriority('1');
    $originateMsg->setExtension($request['dial']);
    $a->send($originateMsg);

    while(true) {
        if( $a->process() ) break;
        usleep(1000);
    }

    $a->close();

    echo 'OK';
    ob_end_flush();
    flush();
}

$a->process() calls the following method:

/**
 * Main processing loop. Also called from send(), you should call this in
 * your own application in order to continue reading events and responses
 * from ami. 
 */
public function process()
{
    $msgs = $this->getMessages();
    foreach ($msgs as $aMsg) {
        if ($this->_logger->isDebugEnabled()) {
            $this->_logger->debug(
                '------ Received: ------ ' . "\n" . $aMsg . "\n\n"
            );
        }
        $resPos = strpos($aMsg, 'Response:');
        $evePos = strpos($aMsg, 'Event:');
        if (($resPos !== false) && (($resPos < $evePos) || $evePos === false)) {
            $response = $this->_messageToResponse($aMsg);
            $this->_incomingQueue[$response->getActionId()] = $response;
        } else if ($evePos !== false) {
            $event = $this->_messageToEvent($aMsg);
            $response = $this->findResponse($event);
            if ($response === false || $response->isComplete()) {
                $this->dispatch($event);
            } else {
                $response->addEvent($event);
            }
        } else {
            // broken ami.. sending a response with events without
            // Event and ActionId
            $bMsg = 'Event: ResponseEvent' . "\r\n";
            $bMsg .= 'ActionId: ' . $this->_lastActionId . "\r\n" . $aMsg;
            $event = $this->_messageToEvent($bMsg);
            $response = $this->findResponse($event);
            $response->addEvent($event);
        }
        if ($this->_logger->isDebugEnabled()) {
            $this->_logger->debug('----------------');
        }
    }
}

$a->process() then stacks up Event messages, to read these I create an implementation of IEventListener which is also called 'behind the scenes' when $a->process() is called.

class VoipEventStart implements IEventListener
{
    public function handle(EventMessage $event)
    {
        $a = $event->getKeys();

        if( ($a['event'] == "Hangup" || $a['event'] == "HangupRequest") && strpos($a['channel'], 'SIP/') !== FALSE)
        {
            return true;
        }

        return false;
    }
}

The process method reads events from an Asterisk PBX system while a user is on an active call. This means the process loop will last as long as the call lasts.

How would I execute this client-side without the browser looking like it is loading/waiting?


Solution

  • You could use Server Sent Events or some socket implementation but for simplest probably a long-poll strategy would work out well enough.

    For that, send a plain AJAX request from the client and make server only return when the criterion is met. It could be as simple as changing the server side code as follows:

    while(true) {
        if( $a->process() ) break;
        usleep(1000);
    }
    echo 'OK';
    ob_end_flush();
    flush();
    

    And the client should send a simple GET request to the PHP file that starts the above loop. This way when a response comes back to this request, you know process() return true.

    The client code could look something like this:

    <div>Call status: <span id="status">In call</span></div>
    <script>
      $.get('/check_call_status.php?callerId=123', function(response) {
        if(response === 'OK') {
          $('#staus').html('finished');
        }
      });
    </script>
    

    Of course, this could be more sophisticated to deal with timeouts. E.g. if there's no response to the long-poll request for a given time period, restart it - i.e. abort the request and send it again to avoid client or server timeouts.