SSE连接重连后抛出异常但功能正常的问题咨询与处理
基于SSE的订单实时通知接口异常问题
我实现了一个基于SSE的订单实时通知接口,订单数据来自RabbitMQ队列,接口代码如下:
@GetMapping(value = "/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter subscribeToOrders(@AuthenticationPrincipal UserEntity user) { if (user.getShop() == null) { throw new ShopNotFoundException("This shop doesn't exist"); } return sseService.createEmitter(user.getShop().getId()); }
对应的SseService管理代码如下:
@Service public class SseService { private final ConcurrentHashMap<Long, List<SseEmitter>> emittersByShop = new ConcurrentHashMap<>(); public SseEmitter createEmitter(Long shopId) { SseEmitter emitter = new SseEmitter(100_000L); emitter.onCompletion(() -> removeEmitter(shopId, emitter)); emitter.onError(e -> removeEmitter(shopId, emitter)); emittersByShop.computeIfAbsent(shopId, k -> new CopyOnWriteArrayList<>()).add(emitter); return emitter; } private void removeEmitter(Long shopId, SseEmitter emitter) { emittersByShop.computeIfPresent(shopId, (k, emitters) -> { emitters.remove(emitter); return emitters.isEmpty() ? null : emitters; }); } public void sendOrderToShop(OrderDTO order) { List<SseEmitter> emitters = emittersByShop.get(order.shopId()); if (emitters == null) { return; } List<SseEmitter> deadEmitters = new CopyOnWriteArrayList<>(); emitters.forEach(emitter -> { try { emitter.send(SseEmitter.event().data(order, MediaType.APPLICATION_JSON)); } catch (Exception e) { deadEmitters.add(emitter); } }); emitters.removeAll(deadEmitters); deadEmitters.forEach(SseEmitter::complete); } }
异常触发流程
- 已认证用户建立SSE连接
- 正常接收订单
- 关闭连接
- 重新建立SSE连接
- 正常接收新订单,但此时抛出
ServletException和AuthorizationDeniedException,异常信息为:Servlet.service() for servlet [dispatcherServlet] in context with path [/api] threw exception [Unable to handle the Spring Security Exception because the response is already committed.] with root cause
尽管抛出异常,功能仍正常运行,用户可正常接收数据,SSE流表现正常。我使用存储在Cookie中的JWT进行认证,该端点对所有已认证用户开放,排除角色权限问题。
已尝试的解决方法
为抑制异常,我实现了自定义RestAuthenticationEntryPoint:
@Component public class RestAuthenticationEntryPoint extends BasicAuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) { if (!response.isCommitted()) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); } } @Override public void afterPropertiesSet() { setRealmName("CompraAca"); super.afterPropertiesSet(); } }
同时尝试自定义AccessDeniedHandler,但异常仍存在。
问题
- 为何仅在重连后抛出该异常?
- 这是SSE与Spring Security的预期行为吗?
- 在功能正常的前提下,如何正确处理或抑制该异常以避免日志污染?
内容的提问来源于stack exchange,提问作者Gerónimo González Martino
相关产品推荐
相关产品推荐

