Reputation: 29567
Please note: the obvious answer here is "fix the thing that is taking 20 minutes to complete". That's not the answer I'm looking for, because I have no control over the actual mechanism (see WidgetProcessor
down below) that is the root bottleneck here.
I have a Grails (2.4.3) app that puts most of the processing on the client-side via HTML5 and JS. I now have a need for the user to click a button, and for this event to kick off a very long (anywhere from 3 - 20 minutes), asynchronous process, that ultimately should result with the user's screen being dynamically (sans page refresh) updated with a result.
On the client-side (jQuery):
$(".my-button").click(function(e){
var btn = $(this);
$.ajax({
type: 'GET',
url: "/myapp/fizz/kickOffBigJob",
data: {fizz: $(btn).attr('fizz')},
success: function (data) {
}
})
});
On click, this send to my FizzController#kickOffBigJob()
method:
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))
}
}
Inside 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
}
}
And finally:
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)
}
}
So I now have 2 problems:
result
' we compute inside WidgetJob#run()
back to the Grails controller (FizzController
); andresult
' from the Grails controller back to the client-side in such a way that, without a page refresh, the user's UI suddenly updates with the 'result
' value.Any ideas as to how I could accomplish this?
Upvotes: 1
Views: 215
Reputation: 29567
This is possible with a number of technologies, but the cleanest is probably the following:
jssh
library (or Atmosphere, or any of the other Java websocket libraries) to create a websocket between the client browser and the Grails app; store a reference to each open websocket in a hashmap inside the Grails controller. That is, every time the Grails controller uses one of these libraries to create a new websocket, store off a reference to it in a map/cache somewhereresult
as one of its params
argumentsNow then, when the Grails controller receives a request for kicking off the long job, it creates an open websocket with the client-side, stored a reference to this websocket in a hashmap (a property/field on the controller itself), and then delegates execution off to the webservice as defined above.
The webservice receives this request, passes it to an executor service and returns an HTTP 200 back to the Grails server instantly. Meanwhile, the executor service chugs away at processing a result. Some 20 minutes later, the result is calculated, and sends it off to the Grails app's action that accepts the result. This action exists inside the same controller as before.
This action looks up the open websocket connection in the hashmap, finds it, and sends the result back to the client-side.
Upvotes: 0