Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid single route in Laravel for Server Sent Events

So I am trying to use Server Sent Events in my laravel app but the problem is that in SSE, you can only specify single URL as in:

var evtSource = new EventSource("sse.php");

The problem is that I want to send events from different parts/controllers of the entire application not just single sse.php file for example. Example:

// AuthController.php
public function postLogin(Request $request) {
  // login logic

  // SEND SSE EVENT ABOUT NEW USER LOGIN
}

// FooController.php
public function sendMessage() {
  // SEND SSE EVENT ABOUT NEW MESSAGE
}

The only idea that comes to my mind is that from these controllers, create temporary files with event text and then read those text files in sse.php file, send events and then delete files. However, this doesn't look like perfect solution. Or maybe hook sse.php file with Laravel events mechanism somehow but that seems kinda advanced.

I also don't mind creating multiple event source objects with different php files like sse.php but problem is that those controllers methods don't necessarily just send events alone, they do other work as well. For example, I cannot use postLogin action directly in SSE object because it also performs the login logic not just sending event.

Has anyone had similar problem and how to tackle with it please ?

like image 896
dev02 Avatar asked Jul 18 '17 18:07

dev02


People also ask

How do you stop responding to events in a stream?

Cancel an event stream # To cancel a stream from the server, respond with a non text/event-stream Content-Type or return an HTTP status other than 200 OK (e.g. 404 Not Found ). Both methods will prevent the browser from re-establishing the connection.

Are laravel events asynchronous?

Laravel supports asynchronous events by allowing Listeners (which are classes) to implement a ShouldQueue marker interface to indicate that if a queue system is available the Listener should not execute immediately but a queue entry should be made that will, in turn, execute the Listener "later".

What is a EventSource?

The EventSource interface is web content's interface to server-sent events. An EventSource instance opens a persistent connection to an HTTP server, which sends events in text/event-stream format. The connection remains open until closed by calling EventSource.


2 Answers

Never used SSE but, according to their docs, two solutions come to my mind:

1.Define multiple EventSource objects and handle them differently in your js:

var loginSource = new EventSource("{!! url("/loginsse") !!}");
var messageSource = new EventSource("{!! url("/messagesse") !!}");

2. Inside your sse.php file, check for updates from the others controller:

//sse.php called function
while(1) {
  $login = AuthController::postLogin($request);
  if ($login) return $login;

  $msg = FooController::sendMessage();
  if ($msg) return $msg;
  // ...
}

You will need to make sendMessage() and postLogin(Request $request) static methods.

Consider also the adoption of some libraries to use SSE with laravel: a quick google search provided me several possibilities.

like image 97
gbalduzzi Avatar answered Sep 23 '22 11:09

gbalduzzi


You could use Laravel's events and create a listener that would combine all the different events from your app into a single stream, and then you'd have a single controller that would emit that stream of events to the client. You could generate an event anywhere in your code and it would be streamed to the client. You'd need some sort of shared FIFO buffer to allow the communication between the listener and controller, listener would write to it, and controller would read it and send SSE. The simplest solution would be to use just a plain log file for this.

Also Laravel has built-in broadcasting feature using Laravel Echo, so maybe that could help? I'm not sure if Echo is using SSE (have zero experience with both), but it does pretty much the same thing...

Updated: Let me try to add an example:

1) First we need to create an event and a listener, e.g.:

php artisan make:event SendSSE
php artisan make:listener SendSSEListener --event=SendSSE

2) Your event class is just a wrapper around the data that you wish to pass to the listener. Add to SendSSE class as many properties as you need, but lets pass just a string message for this example:

    public function __construct($msg)
    {
        $this->message = $msg;
    } 

    public function getMessage()
    {
        return $this->message;
    } 

3) Now you can fire this event like this:

event(new \App\Events\SendSSE('Hello there'));

4) Your listener's handle() method will receive it and then you need to push it into a FIFO buffer that you'll use to communicate with the SSE controller. This can be as simple as writing to a file, and then reading from it in your controller, or you can use a DB or linux fifo pipe or shared memory or whatever you wish. I'll leave that part to you and presume that you have a service for it:

public function handle(\App\Events\SendSSE $event)
{
    // let's presume you have a helper method that will pass 
    // the message to the SSE Controller
    FifoBufferService::write($event->getMessage());
}

5) Finally, in your SSE Controller you should read the fifo buffer and when a new message comes pass it to the client:

while(1) {
    // if this read doesn't block, than you'll probably need to sleep()
    if ($new_msg = FifoBufferService::read()) {     
        $this->sendSSE($new_msg); 
    }
}
like image 24
ivanhoe Avatar answered Sep 19 '22 11:09

ivanhoe