Server Sent Events with Grails - 如何防止控制器关闭连接

Server Sent Events with Grails - How to prevent the controller from closing the connection

这个问题是关于:

我正在尝试在 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]

我得到的错误是由于在控制器中完成 'heartbeat' 操作后连接关闭。 如果我添加一个 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 服务器,两者的结果相同;控制器操作完成后立即发送所有消息。

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

可以找到插件源here

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

关键部分是这样的:

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 执行此操作:

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

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

asyncContext.start {
      while(true) {
          // write event
      }   
}