Spring MVC Streaming и обработка запросов SSE

Spring MVC Streaming и обработка запросов SSE

1. Вступление

Это простое руководство демонстрирует использование нескольких асинхронных и потоковых объектов в Spring MVC 5.x.x.

В частности, мы рассмотрим три основных класса:

  • ResponseBodyEmitter

  • SseEmitter

  • StreamingResponseBody

Также мы обсудим, как взаимодействовать с ними с помощью клиента JavaScript.

2. ResponseBodyEmitterс

ResponseBodyEmitter обрабатывает асинхронные ответы.

Кроме того, он представляет собой родительский элемент для ряда подклассов, один из которых мы рассмотрим подробнее ниже.

2.1. Сторона сервера

Лучше использоватьResponseBodyEmitter вместе с его собственным выделенным асинхронным потоком и обернуть егоResponseEntity (в который мы можем напрямую вставитьemitter):

@Controller
public class ResponseBodyEmitterController {

    private ExecutorService executor
      = Executors.newCachedThreadPool();

    @GetMapping("/rbe")
    public ResponseEntity handleRbe() {
        ResponseBodyEmitter emitter = new ResponseBodyEmitter();
        executor(() -> {
            try {
                emitter.send(
                  "/rbe" + " @ " + new Date(), MediaType.TEXT_PLAIN);
                emitter.complete();
            } catch (Exception ex) {
                emitter.completeWithError(ex);
            }
        });
        return new ResponseEntity(emitter, HttpStatus.OK);
    }
}

Итак, в приведенном выше примереwe can sidestep needing to use CompleteableFutures, более сложные асинхронные обещания или использование аннотации@Async.

Вместо этого мы просто объявляем нашу асинхронную сущность и оборачиваем ее в новыйThread, предоставленныйExecutorService.

2.2. Сторона клиента

Для использования на стороне клиента мы можем использовать простой метод XHR и вызывать наши конечные точки API, как в обычной операции AJAX:

var xhr = function(url) {
    return new Promise(function(resolve, reject) {
        var xmhr = new XMLHttpRequest();
        //...
        xmhr.open("GET", url, true);
        xmhr.send();
       //...
    });
};

xhr('http://localhost:8080/javamvcasync/rbe')
  .then(function(success){ //... });

3. SseEmitterс

SseEmitter is actually a subclass of ResponseBodyEmitter and provides additional Server-Sent Event (SSE) поддерживает "из коробки".

3.1. Сторона сервера

Итак, давайте кратко рассмотрим пример контроллера, использующего эту мощную сущность:

@Controller
public class SseEmitterController {
    private ExecutorService nonBlockingService = Executors
      .newCachedThreadPool();

    @GetMapping("/sse")
    public SseEmitter handleSse() {
         SseEmitter emitter = new SseEmitter();
         nonBlockingService.execute(() -> {
             try {
                 emitter.send("/sse" + " @ " + new Date());
                 // we could send more events
                 emitter.complete();
             } catch (Exception ex) {
                 emitter.completeWithError(ex);
             }
         });
         return emitter;
    }
}

Довольно стандартный тариф, но мы заметим несколько отличий между ним и нашим обычным контроллером REST:

  • Сначала мы возвращаемSseEmitter

  • Кроме того, мы оборачиваем основную информацию об ответе в собственныйThread

  • Наконец, мы отправляем ответную информацию, используя emitter.send()

3.2. Сторона клиента

На этот раз наш клиент работает немного иначе, поскольку мы можем использовать постоянно подключенную библиотекуServer-Sent Event:

var sse = new EventSource('http://localhost:8080/javamvcasync/sse');
sse.onmessage = function (evt) {
    var el = document.getElementById('sse');
    el.appendChild(document.createTextNode(evt.data));
    el.appendChild(document.createElement('br'));
};

4. StreamingResponseBodyс

Наконец,we can use StreamingResponseBody to write directly to an OutputStream before passing that written information back to the client using a ResponseEntity.

4.1. Сторона сервера

@Controller
public class StreamingResponseBodyController {

    @GetMapping("/srb")
    public ResponseEntity handleRbe() {
        StreamingResponseBody stream = out -> {
            String msg = "/srb" + " @ " + new Date();
            out.write(msg.getBytes());
        };
        return new ResponseEntity(stream, HttpStatus.OK);
    }
}

4.2. Сторона клиента

Как и раньше, мы воспользуемся обычным методом XHR для доступа к указанному выше контроллеру:

var xhr = function(url) {
    return new Promise(function(resolve, reject) {
        var xmhr = new XMLHttpRequest();
        //...
        xmhr.open("GET", url, true);
        xmhr.send();
        //...
    });
};

xhr('http://localhost:8080/javamvcasync/srb')
  .then(function(success){ //... });

Затем давайте посмотрим на некоторые успешные применения этих примеров.

5. Собираем все вместе

После того, как мы успешно скомпилировали наш сервер и запустили наш клиент (доступ к предоставленномуindex.jsp), мы должны увидеть в нашем браузере следующее:

image И в нашем терминале:

 

image

Мы также можем напрямую вызывать конечные точки и видеть их потоковые ответы в нашем браузере.

6. Заключение

ХотяFuture иCompleteableFuture оказались надежными дополнениями к Java и Spring, теперь в нашем распоряжении есть несколько ресурсов для более адекватной обработки асинхронных и потоковых данных для веб-приложений с высокой степенью параллелизма.

Наконец, просмотрите полные примеры кодаover on GitHub.