Laravel 5:使用服务器发送的事件将消息推送到浏览器 [英] Laravel 5: Use Server-Sent Events to push messages to browser

查看:74
本文介绍了Laravel 5:使用服务器发送的事件将消息推送到浏览器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我查看了此问题

I've looked at This SO question and This article to try and implement Server-Sent Events in Laravel 5. Though, between the two, I can't figure how push updates to the client based on an event. In this case, the event is a ClientException being thrown. A ClientException in my application is caused by a user made error. When one is thrown, I want to push an update to the client that populates a universal error panel.

这就是我的刀锋:

<script>
    var source = new EventSource("{{ route('globalmessages') }}");
    source.addEventListener("message", function(e)
    {
        $("#errors").html(e);
    }, false);
</script>

<div id="errors">
</div>

EventSource成功命中了控制器动作:

The EventSource successfully hits the the controller action:

public function pushErrors()
{
    $response = new StreamedResponse(function()
    {
        $errors = ???; // How do I populate this
        if (!empty($error))
        {
            echo $error;
            ob_flush();
            flush();
        }
        else
        {
            \Log::info("No errors to push");
        }
    });

    $response->headers->set('Content-Type', 'text/event-stream');
    return $response;
}

错误处理发生在Handler.php@render中:

And the error handling happens in Handler.php@render:

if ($e instanceof ClientException)
    {
        $message = $e->getMessage(); // Need to send this to client
        return \Response::json([]);
    }
    else
    {
        return parent::render($request, $e);
    }

因此,我需要做的是以某种方式将错误提供给控制器路由.我尝试使用单例,但无法正常工作.我尝试给控制器一个字段来保存它,但这始终是空的.

So, what I need to do is somehow give the errors to the controller route. I tried using a singleton, but I couldn't get that to work. I tried giving the controller a field to hold it, but that was always empty.

最重要的是,此当前实现似乎每5秒运行一次.我希望能够在抛出ClientException来推送更新时调用一个函数.

On top of that, this current implementation seems to be running every 5 seconds. I'd like to be able to call a function when the ClientException is thrown to push the update.

感谢您的帮助.

更多信息:

这是呈现我的页面的控制器动作:

Here's the controller action that renders my page:

class HomeController
{
    public function getHome()
    {
        return view('home');
    }
}

我的控制器使用无限循环:

My controller using an infinite loop:

class MainController extends RouteController
{
    public function pushErrors()
    {
        $response = new StreamedResponse();
        $response->headers->set('Content-Type', 'text/event-stream');
        $response->headers->set('Cach-Control', 'no-cache');

        $response->setCallback(
            function()
            {
                while (true)
                {
                    $error = MessageQueue::dequeue();
                    if (!empty($error))
                    {
                        echo 'data: ' . $error. "\n\n";
                        ob_flush();
                        flush();
                    }
                    sleep(1);
                }
            });

        return $response;
    }
}

如果我退出while循环,此代码将起作用,因为消息被推送,连接断开,然后重新建立并再次推送.使用while循环,页面仅会挂起.

If I take out the while loop, this code works, in that the message is pushed, the connection dropped, then re-established and pushed again. With the while loop, the page just hangs.

我的路线:

+--------+----------+----------------+----------------+--------------------------------------------------+------------+
| Domain | Method   | URI            | Name           | Action                                           | Middleware |
+--------+----------+----------------+----------------+--------------------------------------------------+------------+
|        | GET|HEAD | globalmessages | globalmessages | App\Http\Controllers\MainController@pushErrors   |            |
|        | GET|HEAD | /              | /              | App\Http\Controllers\HomeController@getHome      |            |
|        | POST     | login          | login          | App\Http\Controllers\HomeController@postLogin    |            |
+--------+----------+----------------+----------------+--------------------------------------------------+------------+

HomeController @ postLogin是我调用的生成ClientException的路由.

HomeController@postLogin is the route that I call to that generates the ClientException.

推荐答案

您的控制器缺少本文中的关键功能.在本文中,控制器正在进行无限循环.在无限循环中,将检查数据的相似性,如果数据已更改,则将刷新并写入缓冲区,以便客户端JS可以读取它.

Your controller is missing a key function from the article. In the article, the controller is doing an endless loop. In the endless loop, the data is checked for similarity, if the data has changed then the buffer is flushed and written so that the client JS can read it.

我将如何实现这一点,就是创建一个包含所有ServerSentEvent的表,然后在我的控制器中查询该表并检索尚未发送的行.

How I would implement this is to create a table that holds all your ServerSentEvents, then in my controller query from that table and retrieve the rows which have not yet been sent.

在此示例中,我们将检查$data数组中是否有任何数据,然后将其发送给客户端.

In this example, we will check if there is any data in our $data array, and send it to the client if there is.

DB::table('ServerSentEvents')->where('sent', 0)->get();

如果查询没有返回行,将返回一个空数组.

Will return a empty array if there are no rows returned from the query.

public function pushErrors() {

$response = new Symfony\Component\HttpFoundation\StreamedResponse(function() {

    $data = $this->getData();

    while (true) {
        if (!empty($data)) {
            echo 'data: ' . json_encode($data) . "\n\n";
            ob_flush();
            flush();

            /* update the table rows as sent */
            $ids = [];
            foreach($data as $event){
                $ids[] = $event->id;
            }
            DB::table('ServerSentEvents')->whereIn('id', $ids)->update('sent', 1);              
        }

        //sleep for 3 seconds
        sleep(3);

        //requery for new events that need to be sent
        $data = $this->getData();
    }

});

    $response->headers->set('Content-Type', 'text/event-stream');
    return $response;
}

public function getData(){
    $data = DB::table('ServerSentEvents')->where('sent', 0)->get();
    return $data;
}

发生ClientException时,您还需要向数据库中插入新行:

You also need to insert new rows into your database when the ClientException occurs:

if ($e instanceof ClientException)
{
    DB::table('ServerSentEvents')->insert(['message' => $e->getMessage(), 'sent' => 0]);        
}
else
{
    return parent::render($request, $e);
}

根据事件所需的信息类型,可能需要在ServerSentEvents表中添加或删除更多列.

Depending on what kind of information you want based on your events you may need to add or remove more columns to the ServerSentEvents table.

ServerSentEvents表是您需要创建的表,这里没有提供结构,因为您可能需要在行中保存更多信息,并且从数据库中提取数据的总体概念是观点.

The ServerSentEvents table is a table you will need to create, I didn't provide a structure here because there may be more information you want to save in the rows, and the overall concept of pulling the data from the database is the point.

这篇关于Laravel 5:使用服务器发送的事件将消息推送到浏览器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆