Длительные асинхронные вызовы и отправка сервера в Grails

Обратите внимание: очевидным ответом здесь будет "исправить то, что занимает 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)
    }
}

Итак, у меня сейчас 2 проблемы:

  1. Как передать 'result', который мы вычисляем внутри WidgetJob#run(), обратно в контроллер Grails (FizzController); и
  2. Как динамически передать значение "result" из контроллера Grails обратно на сторону клиента таким образом, чтобы без обновления страницы пользовательский интерфейс пользователя внезапно обновлялся со значением "result".

Любые идеи относительно того, как я могу это сделать?


person smeeb    schedule 19.09.2014    source источник
comment
сомневаюсь, что вы можете сделать это через асинхронный режим, так как это запускает отдельный процесс. Вы можете получить асинхронное задание для обновления таблицы базы данных, которую ваш интерфейс опрашивает на наличие обновлений. внутренний сервер и результаты в реальном времени.   -  person V H    schedule 19.09.2014


Ответы (1)


Это возможно с рядом технологий, но, вероятно, наиболее чистой является следующая:

  • Используйте библиотеку jssh (или Atmosphere, или любую другую библиотеку веб-сокетов Java), чтобы создать веб-сокет между браузером клиента и приложением Grails; сохранить ссылку на каждый открытый веб-сокет в хэш-карте внутри контроллера Grails. То есть каждый раз, когда контроллер Grails использует одну из этих библиотек для создания нового веб-сокета, сохраняйте ссылку на него где-нибудь в карте/кэше.
  • Добавьте метод/действие в контроллер Grails, который принимает последний result в качестве одного из своих params аргументов.

Теперь, когда контроллер Grails получает запрос на запуск длинной задачи, он создает открытый веб-сокет на стороне клиента, сохраняет ссылку на этот веб-сокет в хэш-карте (свойство/поле на самом контроллере), а затем делегирует выполнение веб-службе, как определено выше.

Веб-служба получает этот запрос, передает его службе-исполнителю и мгновенно возвращает HTTP 200 обратно на сервер Grails. Тем временем служба-исполнитель усердно обрабатывает результат. Примерно через 20 минут результат вычисляется и отправляется в действие приложения Grails, которое принимает результат. Это действие существует внутри того же контроллера, что и раньше.

Это действие ищет открытое соединение через веб-сокет в хэш-карте, находит его и отправляет результат обратно на сторону клиента.

person smeeb    schedule 21.09.2014