Estoy tratando de establecer un SseEmitter primavera para enviar una secuencia de cambios del estado de un trabajo en ejecución. Parece estar funcionando, pero:
Siempre que llamo emitter.complete()
en mi código de servidor Java, el Javascript EventSource
cliente llama a la registrada onerror
función y luego llama a mi Java punto final de nuevo con una nueva conexión. Esto sucede tanto en Firefox y Chrome.
Es probable que pueda enviar un mensaje explícito "de fin de datos" de Java y luego detectar que la llamada y eventSource.close()
en el cliente, pero hay una manera mejor?
¿Cuál es el propósito de emitter.complete()
que en ese caso?
Además, si siempre tengo que terminar la conexión en el cliente final, entonces creo que todas las conexiones en el lado del servidor se dará por terminado por cualquiera de un tiempo de espera o un error de escritura, en cuyo caso probablemente quiero enviar manualmente de nuevo un latido del corazón de algunas clase cada pocos segundos?
Se siente como que estoy perdiendo algo si estoy teniendo que hacer todo esto.
He añadido lo siguiente a mi solicitud de inicio de primavera para activar la conexión SSE close()
Lado del servidor:
- Crear un simple controlador que devuelve SseEmitter.
- Envolver la lógica backend en un único servicio ejecutor hilo.
- Envíe sus eventos a la SseEmitter.
Enviar completa en un evento de tipo completo a través de la SseEmitter.
@RestController public class SearchController { @Autowired private SearchDelegate searchDelegate; @GetMapping(value = "/{customerId}/search") @ResponseStatus(HttpStatus.OK) @ApiOperation(value = "Search Sources", notes = "Search Sources") @ApiResponses(value = { @ApiResponse(code = 201, message = "OK"), @ApiResponse(code = 401, message = "Unauthorized") }) @ResponseBody public SseEmitter search(@ApiParam(name = "searchCriteria", value = "searchCriteria", required = true) @ModelAttribute @Valid final SearchCriteriaDto searchCriteriaDto) throws Exception { return searchDelegate.route(searchCriteriaDto); } } @Service public class SearchDelegate { public static final String SEARCH_EVENT_NAME = "SEARCH"; public static final String COMPLETE_EVENT_NAME = "COMPLETE"; public static final String COMPLETE_EVENT_DATA = "{\"name\": \"COMPLETED_STREAM\"}"; @Autowired private SearchService searchService; private ExecutorService executor = Executors.newCachedThreadPool(); public SseEmitter route(SearchCriteriaDto searchCriteriaDto) throws Exception { SseEmitter emitter = new SseEmitter(); executor.execute(() -> { try { if(!searchCriteriaDto.getCustomerSources().isEmpty()) { searchCriteriaDto.getCustomerSources().forEach(customerSource -> { try { SearchResponse searchResponse = searchService.search(searchCriteriaDto); emitter.send(SseEmitter.event() .id(customerSource.getSourceId()) .name(SEARCH_EVENT_NAME) .data(searchResponse)); } catch (Exception e) { log.error("Error while executing query for customer {} with source {}, Caused by {}", customerId, source.getType(), e.getMessage()); } }); }else { log.debug("No available customerSources for the specified customer"); } emitter.send(SseEmitter.event(). id(String.valueOf(System.currentTimeMillis())) .name(COMPLETE_EVENT_NAME) .data(COMPLETE_EVENT_DATA)); emitter.complete(); } catch (Exception ex) { emitter.completeWithError(ex); } }); return emitter; } }
Lado del cliente:
- Puesto que especificamos la
name
del caso en nuestraSseEmitter
, un evento serán enviados en el navegador a la escucha para el nombre del evento especificado; el código fuente del sitio web debe utilizaraddEventListener()
para detectar los eventos mencionados. ( Nota: Elonmessage
gestor se llama si no se especifica el nombre del evento para un mensaje ) - Llame a la
EventSource
delCOMPLETE
caso para liberar la conexión del cliente.
https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
var sse = new EventSource('http://localhost:8080/federation/api/customers/5d96348feb061d13f46aa6ce/search?nativeQuery=true&queryString=*&size=10&customerSources=1,2,3&start=0');
sse.addEventListener("SEARCH", function(evt) {
var data = JSON.parse(evt.data);
console.log(data);
});
sse.addEventListener("COMPLETE", function(evt) {
console.log(evt);
sse.close();
});