在Grails中长时间运行异步调用和服务器推送

时间:2014-09-19 12:30:50

标签: jquery grails asynchronous websocket server-push

请注意:这里显而易见的答案是"修复需要20分钟才能完成的事情"。这不是我正在寻找的答案,因为我无法控制这里的根本瓶颈的实际机制(参见下面的WidgetProcessor)。

我有一个Grails(2.4.3)应用程序,它通过HTML5和JS将大部分处理放在客户端。我现在需要用户点击一个按钮,并且这个事件开始很长时间(3到20分钟),异步过程,最终应该导致用户的屏幕动态( sans页面刷新)用结果更新。

在客户端(jQuery):

$(".my-button").click(function(e){
    var btn = $(this);

    $.ajax({
        type: 'GET',
        url: "/myapp/fizz/kickOffBigJob",
        data: {fizz: $(btn).attr('fizz')},
        success: function (data) {
        }
    })
});

点击后,这会发送到我的FizzController#kickOffBigJob()方法:

class FizzController {
    FizzServiceClient fizzServiceClient = FizzServiceFactory.newFizzServiceClient()

    // ... lots of other stuff

    // This is called when the button above is clicked.
    def kickOffBigJob(params) {
        // Send the request off to a RESTful web service. This service is what
        // handles the asynchronous process and ultimately returns a result
        // (a String). The service endpoint returns immediately ( < 500ms) but
        // the actual result can take up to 20 minutes to be computed.
        fizzServiceClient.kickOffBigJob(convertToWidget(params))
    }
}

内部FizzService#kickOffBigJob()

// This code is deployed to a different JVM/WAR that exposes RESTful endpoints that
// respond to 'fizzServiceClient.kickOffBigJob(Widget)'.
class FizzService {
    ExecutorService executor = initExecutor()

    // This method submits the 'widget' to an executor and then returns an HTTP response
    // to the service client. Note that this response is not the 'result' we're looking
    // for, it's just a quick indication that the request was received OK.
    def kickOffBigJob(Widget widget) {
        WidgetJob widgetJob = new WidgetJob(widget)
        executorService.submit(widgetJob)   // WidgetJob extends Runnable
    }
}

最后:

class WidgetJob implements Runnable {
    Widget widget
    WidgetProcessorFactory wpf = new WidgetProcessorFactory()

    // Constructors, etc.

    @Override
    def run() {
        WidgetProcessor processor = wpf.newWidgetProcess()

        // Where the magic happens; this is what takes up to 20 minutes to
        // compute the 'result'.
        String result = processor.process(widget)
    }
}

所以我现在有两个问题:

  1. 如何传达&#39; result&#39;我们计算内部WidgetJob#run() 返回到Grails控制器(FizzController);和
  2. 如何动态推送&#39; result&#39;从Grails控制器返回到客户端,使得在没有页面刷新的情况下,用户的UI突然更新为&#39; result&#39;值。
  3. 关于如何实现这一目标的任何想法?

1 个答案:

答案 0 :(得分:0)

这可以通过多种技术实现,但最干净的可能是:

  • 使用jssh库(或Atmosphere,或任何其他Java websocket库)在客户端浏览器和Grails应用程序之间创建websocket; 在Grails控制器内的hashmap中存储对每个打开的websocket的引用。也就是说,每次Grails控制器使用其中一个库来创建新的websocket时,在某个地图/缓存中存储对它的引用
  • 向Grails控制器添加一个方法/操作,该控制器接受最终result作为其params参数之一

现在,当Grails控制器收到启动长作业的请求时,它会在客户端创建一个打开的websocket,在hashmap中存储对此websocket的引用(控制器本身的属性/字段) ,然后将执行委托给上面定义的webservice。

Web服务接收此请求,将其传递给执行程序服务,并立即将HTTP 200返回给Grails服务器。同时,遗嘱执行人服务部门在处理结果时突然出现。大约20分钟后,计算结果,并将其发送到接受结果的Grails应用程序操作。此操作存在于与之前相同的控制器中。

此操作在hashmap中查找打开的websocket连接,找到它,然后将结果发送回客户端。