5

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 ?

dev02
  • 1,776
  • 3
  • 24
  • 45
  • I post this as a comment because it is not (and I'm note sure if there is) the perfect solution. I would create 1 API endpoint in laravel that takes some parameter (POST body or query string) that determines what `parts/controllers` you want to serve. A switch case inside your "main" controller if you like. – online Thomas Jul 18 '17 at 18:09
  • @ThomasMoors: How would you tell from event source object which controller method to call as it doesn't seem to have extra arguments parameters `var evtSource = new EventSource("sse.php");`. Also I am afraid, that won't work in the case of `postLogin` action which also performs login logic not just sending event. – dev02 Jul 18 '17 at 18:14
  • My bad: it does not support [POST only GET](https://stackoverflow.com/questions/34261928/server-sent-events-pass-parameter-by-post-method) So you are down to GET request query parameters: `var evtSource = new EventSource("sse.php?whatIwant=theSpecialController");` – online Thomas Jul 18 '17 at 18:19
  • @ThomasMoors: Thanks and true but again that won't work I think for any controller action that also performs certain other tasks other than just sending events, example `postLogin` method which first tries to login user first, if successful only then sends event. – dev02 Jul 18 '17 at 18:23
  • I think you are missing the fact, that the code that should send the SSE and the code which responds to the current request are living in two completely different processes. The SSE code will need to be kept alive by an infinite loop, and the code in your controllers lives only for the duration of a single request. A better solution will be to use Laravel Echo with the Redis backend. – Daniel Alexandrov Jul 22 '17 at 15:37
  • You can use a query string when calling `EventSource()` and parse the query string at `php` to use the same `sse.php` for different streams, see [Change source (url) of Server-Sent event](https://stackoverflow.com/q/42446195/) – guest271314 Jul 24 '17 at 01:27

2 Answers2

3

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.

gbalduzzi
  • 9,356
  • 28
  • 58
2

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); 
    }
}
ivanhoe
  • 4,593
  • 1
  • 23
  • 22
  • Yes I have reason to believe that events and listeners can be used somehow but now sure how, a simple example would have been helpful. Thanks – dev02 Jul 19 '17 at 08:50
  • @dev02 added some examples. Please note that I was writing this from a top of my head, haven't tested any of the code. It's meant just as an illustration of the idea. For the details on events/listeners check https://laravel.com/docs/5.4/events#defining-events – ivanhoe Jul 24 '17 at 19:17