I am building an application with a Spring WebFlux backend and an Angular frontend. My backend sends Server-Sent Events (SSE), but for some reason, the Angular app is not receiving these events, even though the same backend works perfectly when tested in Postman or with curl.
Here’s what I’ve implemented so far: Backend (WebFlux): The WebFlux controller sends SSE using MediaType.TEXT_EVENT_STREAM_VALUE:
@GetMapping(value = "/purchasedItems",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
@ResponseStatus(HttpStatus.ACCEPTED)
public Flux<PurchasedItemData> findAllPurchasedItem(ServerHttpResponse response){
response.getHeaders().add("Cache-Control", "no-cache, no-transform");
response.getHeaders().add("Connection", "keep-alive");
return purchaseService.getPurchasedItems()
.doOnNext(item -> System.out.println("Sending item: " + item.getName()))
.doOnError(error -> System.out.println("Error sending items: "+ error.getMessage()))
.doFinally(signalType -> System.out.println("Streaming finished: " + signalType));
}
}
}
When I test this endpoint with curl or Postman, I see the events streaming correctly.
Frontend (Angular): I’m using the native EventSource API to consume SSE:
private apiUrl = 'http://localhost:8080/paypro/purchasedItems';
fetchPurchasedItems(): Observable<any> {
return new Observable(observer => {
const eventSource = new EventSource(this.apiUrl);
eventSource.onmessage = (event) => {
try {
debugger;
const data = JSON.parse(event.data);
console.log('Received SSE data:', data); // Debugging line
observer.next(data);
} catch (error) {
console.error('Error parsing SSE data:', event.data, error);
observer.error(error);
}
};
eventSource.addEventListener('message', (evt) => {
console.log('Received SSE data:', evt.data); // Debugging line
observer.next(evt);
});
eventSource.onerror = (error) => {
console.error('EventSource error:', error);
debugger;
observer.error(error);
eventSource.close();
};
return () => eventSource.close();
});
}
In my Angular component:
public loadData() {
// this.purchasedItems$ = this.service.fetchPurchasedItems();
this.isLoading = true;
this.service.fetchPurchasedItems().subscribe({
next: (data) => {
console.log('Fetched data:', data);
debugger;
this.purchasedItems=[...this.purchasedItems,...data];
this.cd.detectChanges(); // Trigger change detection
},
error: (err) => {
console.error('Error fetching data', err);
this.isLoading = false;
}
});
}
Proxy Configuration: In development, I’m using a proxy configuration to redirect /api/events to http://localhost:8080/events:
proxy.conf.json:
{
"/paypro/*": {
"target": "http://localhost:8080",
"secure": false,
"logLevel": "debug",
"changeOrigin": true
}
}
Problem: 1.In the Angular app, the EventSource immediately triggers the onerror event with no data received.
2.The browser's Network tab shows the request to /api/events is made, but the connection closes immediately without any SSE data.
3.There are no errors in the backend logs.
What I’ve Tried: 1.Testing the /events endpoint with curl and Postman—works perfectly.
2.Ensuring the response header includes Content-Type: text/event-stream.
3.Double-checking the proxy configuration in Angular.
4.Adding CORS settings to the WebFlux backend: java
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("http://localhost:4200");
config.addAllowedMethod("*");
config.addAllowedHeader("*");
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
What could be causing the Angular app to fail when consuming SSE from the WebFlux backend, and how can I fix this issue?
Any help would be greatly appreciated!
I found the issue, and I want to share the solution for future visitors to this thread.
The problem was caused by the use of the annotation:
@ResponseStatus(HttpStatus.ACCEPTED)
This annotation was redundant in my case. It likely caused the connection to be closed prematurely, preventing the application from functioning correctly.
Solution:
After removing the @ResponseStatus(HttpStatus.ACCEPTED), the application started working as expected. If you're facing a similar issue, consider whether this annotation is necessary for your use case, as it might interfere with response handling.