You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

异常触发流程

  1. 已认证用户建立SSE连接
  2. 正常接收订单
  3. 关闭连接
  4. 重新建立SSE连接
  5. 正常接收新订单,但此时抛出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,但异常仍存在。

问题

  1. 为何仅在重连后抛出该异常?
  2. 这是SSE与Spring Security的预期行为吗?
  3. 在功能正常的前提下,如何正确处理或抑制该异常以避免日志污染?

内容的提问来源于stack exchange,提问作者Gerónimo González Martino

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 22:23:09