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:
E o seguinte em nosso terminal:
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.