如何避免 Laravel 中服务器发送事件的单一路由

Posted

技术标签:

【中文标题】如何避免 Laravel 中服务器发送事件的单一路由【英文标题】:How to avoid single route in Laravel for Server Sent Events 【发布时间】:2017-12-23 17:59:16 【问题描述】:

所以我尝试在我的 laravel 应用程序中使用 Server Sent Events,但问题是在 SSE 中,您只能指定单个 URL,如下所示:

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

问题是我想从整个应用程序的不同部分/控制器发送事件,而不仅仅是单个 sse.php 文件。示例:

// 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

我想到的唯一想法是,从这些控制器中创建带有事件文本的临时文件,然后在 sse.php 文件中读取这些文本文件,发送事件,然后删除文件。但是,这看起来并不是完美的解决方案。或者可能以某种方式将 sse.php 文件与 Laravel 事件机制挂钩,但这似乎有点先进。

我也不介意使用不同的 php 文件(如 sse.php)创建多个事件源对象,但问题是这些控制器方法不一定只是单独发送事件,它们还可以做其他工作。例如,我不能直接在 SSE 对象中使用 postLogin 操作,因为它还执行登录逻辑,而不仅仅是发送事件。

有没有人遇到过类似的问题,请问如何解决?

【问题讨论】:

我将此作为评论发布,因为它不是(我注意到是否有)完美的解决方案。我会在 laravel 中创建 1 个 API 端点,它接受一些参数(POST 正文或查询字符串)来确定您想要服务的 parts/controllers。如果您愿意,可以在您的“主”控制器中放置一个开关盒。 @ThomasMoors:您如何从事件源对象中得知要调用哪个控制器方法,因为它似乎没有额外的参数参数var evtSource = new EventSource("sse.php");。另外我担心,这在postLogin 操作的情况下不起作用,它还执行登录逻辑而不仅仅是发送事件。 我的错:它不支持POST only GET所以你只能GET请求查询参数:var evtSource = new EventSource("sse.php?whatIwant=theSpecialController"); @ThomasMoors:谢谢,真的,但我认为对于任何除了发送事件之外还执行某些其他任务的控制器操作,例如postLogin首先尝试登录用户的方法首先,如果成功,则仅发送事件。 我认为您忽略了一个事实,即应该发送 SSE 的代码和响应当前请求的代码存在于两个完全不同的进程中。 SSE 代码需要通过无限循环保持活动状态,并且控制器中的代码仅在单个请求的持续时间内存在。更好的解决方案是使用带有 Redis 后端的 Laravel Echo。 【参考方案1】:

您可以使用 Laravel 的事件并创建一个侦听器,将应用程序中的所有不同事件组合到一个流中,然后您将拥有一个控制器来将该事件流发送到客户端。您可以在代码中的任何位置生成事件,并将其流式传输到客户端。您需要某种共享的 FIFO 缓冲区来允许侦听器和控制器之间的通信,侦听器将写入它,而控制器将读取它并发送 SSE。最简单的解决方案是为此只使用一个普通的日志文件。

Laravel 还内置了使用 Laravel Echo 的广播功能,所以也许这会有所帮助?我不确定 Echo 是否使用 SSE(两者的经验为零),但它的作用几乎相同......

更新:让我尝试添加一个示例:

1) 首先我们需要创建一个事件和一个监听器,例如:

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

2) 您的事件类只是您希望传递给侦听器的数据的包装器。根据需要向 SendSSE 类添加任意数量的属性,但在此示例中只传递一个字符串消息:

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

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

3) 现在你可以像这样触发这个事件:

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

4) 您的侦听器的 handle() 方法将接收它,然后您需要将其推送到一个 FIFO 缓冲区中,您将使用该缓冲区与 SSE 控制器进行通信。这可以像写入文件一样简单,然后在控制器中读取它,或者您可以使用 DB 或 linux fifo 管道或共享内存或任何您想要的东西。我会把这部分留给你,并假设你有它的服务:

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) 最后,在您的 SSE 控制器中,您应该读取 fifo 缓冲区,并在收到新消息时将其传递给客户端:

while(1) 
    // if this read doesn't block, than you'll probably need to sleep()
    if ($new_msg = FifoBufferService::read())      
        $this->sendSSE($new_msg); 
    

【讨论】:

是的,我有理由相信可以以某种方式使用事件和侦听器,但现在确定如何使用,一个简单的示例会有所帮助。谢谢 @dev02 添加了一些示例。请注意,我是从头顶上写的,还没有测试任何代码。它只是作为这个想法的一个例证。有关事件/听众的详细信息,请查看laravel.com/docs/5.4/events#defining-events【参考方案2】:

从未使用过 SSE,但根据他们的文档,我想到了两种解决方案:

1.定义多个EventSource对象并在你的js中以不同方式处理:

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

2。在 sse.php 文件中,检查其他控制器的更新:

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

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

您需要创建 sendMessage()postLogin(Request $request) 静态方法。

还考虑采用一些库来将 SSE 与 laravel 一起使用:快速的 google 搜索为我提供了多种可能性。

【讨论】:

以上是关于如何避免 Laravel 中服务器发送事件的单一路由的主要内容,如果未能解决你的问题,请参考以下文章

谷歌分析事件跟踪:如何避免发送数据进行本地测试?

Laravel 5.3 - 避免在 phpunit 测试中发送松弛通知

带有 Laravel 的标签内的单选和复选框

如何从laravel中保存mysql中的单选按钮值

如何解决 Lumen/Laravel 中的单例?

Laravel 刀片标签标签中的单选按钮