使用Grails的服务器发送事件 - 如何防止控制器关闭连接

时间:2016-09-18 23:14:42

标签: grails server-sent-events

此问题与:Grails Server Sent Event

有关

我正在尝试在grails v2.4上实现SSE,但我无法阻止grails关闭连接。 我所拥有的是:

import org.codehaus.groovy.grails.web.servlet.GrailsApplicationAttributes as GA
class SseController  {
  def heartbeat = {
    response.contentType = 'text/event-stream'
    response.characterEncoding = 'UTF-8'
    response.setHeader('Cache-Control', 'no-cache')
    response.setHeader('Connection', 'keep-alive')
    response << 'data: 12345\n\n'
    response.flushBuffer()

    def grails_request = request.getAttribute(GA.WEB_REQUEST)
    grails_request.setRenderView(false)
  }
}

但如果我这样做,客户浏览器会在订阅频道后报告以下内容:

Open [object Event]
Data:12345
Error [object Event]

我得到的错误是由于在“心跳”之后关闭了连接。动作在控制器中完成。 如果我添加一个while循环来保持动作像这样运行......

import org.codehaus.groovy.grails.web.servlet.GrailsApplicationAttributes as GA
class SseController  {
  def heartbeat = {
    response.contentType = 'text/event-stream'
    response.characterEncoding = 'UTF-8'
    response.setHeader('Cache-Control', 'no-cache')
    response.setHeader('Connection', 'keep-alive')
    response << 'data: 12345\n\n'
    response.flushBuffer()

    def grails_request = request.getAttribute(GA.WEB_REQUEST)
    grails_request.setRenderView(false)

    while(true) {
      sleep(10000)
    }
  }
}

...然后客户端浏览器永远不会收到数据。 response.flushBuffer()命令无效。 连接没有关闭,没关系,但数据不会发送给客户端。

因此,正确的解决方案将涉及摆脱while循环,同时在执行操作后告诉Grails不要关闭连接。

任何人都知道如何做到这一点? 顺便说一句,我尝试使用Tomcat和Jetty服务器,两者的结果相同;控制器动作完成后,所有消息都会立即发送。

1 个答案:

答案 0 :(得分:1)

Server Sent Events has been implemented for Grails 3.2的原生支持。

可以找到插件来源here

如果您希望继续使用Grails 2,可以将此插件移植到Grails 2.但是,如果您打算推出自己的解决方案,那么实现的关键是您需要启动非阻塞异步响应。您可以在插件的RxResultTransformer类中看到这是如何完成的。

关键部分是:

class MyClass 
{
    public static $variable;

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

\MyClass::$variable = null;

echo \MyClass::$variable;

然后,您需要启动另一个定期将数据发送回客户端的容器线程。该插件使用RxJava执行此操作:

webRequest.setRenderView(false)

// Create the Async web request and register it with the WebAsyncManager so Spring is aware
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request)

AsyncWebRequest asyncWebRequest = new AsyncGrailsWebRequest(
        request,
        response,
        webRequest.servletContext)

asyncManager.setAsyncWebRequest(asyncWebRequest)
// Start async processing and create the GrailsAsync object
asyncWebRequest.startAsync()
request.setAttribute(GrailsApplicationAttributes.ASYNC_STARTED, true)
GrailsAsyncContext asyncContext = new GrailsAsyncContext(asyncWebRequest.asyncContext, webRequest)
response.setContentType(CONTENT_TYPE_EVENT_STREAM);
response.flushBuffer()

如果你不想使用RxJava,那么你可以简单地使用while循环或任何适合你的东西。

Observable newObservable = Observable.create( { Subscriber newSub ->
    asyncContext.start {
        // your code here
    }
} as Observable.OnSubscribe)
newObservable.subscribe(subscriber)