请注意:这里显而易见的答案是"修复需要20分钟才能完成的事情"。这不是我想要的答案,因为我无法控制实际的机制(见下面的WidgetProcessor
),这是这里的根本瓶颈。
我有一个Grails(2.4.3)应用程序,它通过HTML5和JS将大部分处理放在客户端。我现在需要用户点击一个按钮,并让这个事件启动一个非常长(从3-20分钟不等)的异步过程,最终应该会导致用户的屏幕动态更新(无页面刷新)。
在客户端(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)
}
}
所以我现在有两个问题:
- 如何将我们在
WidgetJob#run()
内部计算的"result
"返回到Grails控制器(FizzController
);以及 - 如何动态地将'
result
'从Grails控制器推回到客户端,这样,在没有页面刷新的情况下,用户的UI会突然更新为'result
'值
关于我该如何做到这一点,有什么想法吗?
这在许多技术中都是可能的,但最干净的可能是以下技术:
- 使用
jssh
库(或Atmosphere,或任何其他Java网络套接字库)在客户端浏览器和Grails应用程序之间创建网络套接字在Grails控制器内部的hashmap中存储对每个打开的websocket的引用。也就是说,每次Grails控制器使用其中一个库来创建新的websocket时,都要将对它的引用存储在某个映射/缓存中 - 向Grails控制器添加一个方法/操作,该控制器接受最后的
result
作为其params
参数之一
现在,当Grails控制器收到启动长作业的请求时,它会与客户端创建一个打开的websocket,在hashmap(控制器本身的属性/字段)中存储对该websocket的引用,然后将执行委托给上面定义的webservice。
Web服务接收这个请求,将其传递给执行器服务,并立即将HTTP200返回给Grails服务器。与此同时,executor服务在处理一个结果时匆匆忙忙。大约20分钟后,计算出结果,并将其发送给Grails应用程序的接受结果的操作。此操作存在于与以前相同的控制器中。
此操作在hashmap中查找打开的websocket连接,找到它,并将结果发送回客户端。