Spring MVC Streaming e processamento de solicitação SSE

Spring MVC Streaming e processamento de solicitação SSE

1. Introdução

Este tutorial simples demonstra o uso de vários objetos assíncronos e de streaming no Spring MVC 5.x.x.

Especificamente, revisaremos três classes principais:

  • ResponseBodyEmitter

  • SseEmitter

  • StreamingResponseBody

Além disso, discutiremos como interagir com eles usando um cliente JavaScript.

2. ResponseBodyEmitter

ResponseBodyEmitter lida com respostas assíncronas.

Além disso, ele representa um pai para uma série de subclasses - uma das quais veremos mais detalhadamente abaixo.

2.1. Lado do servidor

É melhor usar umResponseBodyEmitter junto com seu próprio thread assíncrono dedicado e empacotado com umResponseEntity (no qual podemos injetaremitter diretamente):

@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);
    }
}

Portanto, no exemplo acima,we can sidestep needing to use CompleteableFutures, promessas assíncronas mais complicadas ou uso da anotação@Async.

Em vez disso, simplesmente declaramos nossa entidade assíncrona e a envolvemos em um novoThread fornecido peloExecutorService.

2.2. Lado do Cliente

Para uso do lado do cliente, podemos usar um método XHR simples e chamar nossos pontos de extremidade da API, como em uma operação AJAX usual:

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) suporta out-of-the-box.

3.1. Lado do servidor

Então, vamos dar uma olhada rápida em um controlador de exemplo aproveitando esta entidade poderosa:

@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;
    }
}

Tarifa bastante normal, mas notaremos algumas diferenças entre este e nosso controlador REST usual:

  • Primeiro, retornamos umSseEmitter

  • Além disso, envolvemos as informações de resposta principais em seus própriosThread

  • Finalmente, enviamos informações de resposta usando emitter.send()

3.2. Lado do Cliente

Nosso cliente funciona um pouco diferente desta vez, pois podemos aproveitar a bibliotecaServer-Sent Event continuamente conectada:

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

Por fim,we can use StreamingResponseBody to write directly to an OutputStream before passing that written information back to the client using a ResponseEntity.

4.1. Lado do servidor

@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. Lado do Cliente

Assim como antes, usaremos um método XHR regular para acessar o controlador acima:

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){ //... });

A seguir, vamos dar uma olhada em alguns usos bem-sucedidos desses exemplos.

5. Trazendo tudo junto

Depois de compilar com sucesso nosso servidor e executar nosso cliente acima (acessando oindex.jsp fornecido), devemos ver o seguinte em nosso navegador:

image E o seguinte em nosso terminal:

 

image

Também podemos ligar diretamente para os pontos de extremidade e ver que as respostas de streaming aparecem em nosso navegador.

6. Conclusão

EmboraFutureeCompleteableFuture tenham comprovado adições robustas ao Java e ao Spring, agora temos vários recursos à nossa disposição para lidar de forma mais adequada com dados assíncronos e de streaming para aplicativos da web altamente concorrentes.

Por fim, verifique os exemplos de código completosover on GitHub.