【问题标题】:Server Sent Events with Grails - How to prevent the controller from closing the connection服务器发送事件与 Grails - 如何防止控制器关闭连接
【发布时间】:2016-09-18 23:14:42
【问题描述】:

此问题与: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 服务器,结果都一样;控制器动作完成后,所有消息都会立即发送。

【问题讨论】:

    标签: grails server-sent-events


    【解决方案1】:

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

    插件源可以在here找到

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

    关键部分是这样的:

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

    【讨论】:

    • 非常感谢格雷姆!你的答案是要走的路,但是现在,由于我们使用 jetty 作为我们的 Web 服务器,我们决定使用它的 org.eclipse.jetty.servlets.EventSourceServlet,我们已将其添加到 web.xml 文件中。到目前为止,它工作得很好。我们一直没有时间将我们的应用程序升级到 Grails v3。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-10-07
    • 2015-06-16
    • 1970-01-01
    • 2013-12-29
    • 2017-05-10
    • 1970-01-01
    • 2014-03-12
    相关资源
    最近更新 更多