IllegalStateException: Cannot forward a response that is already committed

解决响应已提交异常
部署运行你感兴趣的模型镜像
对于初学者来说,一个常见的误解是:当调用 forward() 或者 sendRedirect() 时控制流将会自动跳出原函数。标题所示错误通常是基于此误解而引起的。
示例代码:
protected void doPost() {
    if (someCondition) {
        sendRedirect();
    }
    forward(); // This is STILL invoked when someCondition is true!}
forward() 和 sendRedirect() 与system.exit() 不同,当上例中的 someCondition为true时,很有可能得到此异常:
IllegalStateException: Cannot forward a response that is already committed
为了解决此问题,可以在sendRedirect() / forward() 之后加上 return;
protected void doPost() {
    if (someCondition) {
        sendRedirect();
        return;
    }
    forward();}
    或者把后一个forward() 放入else中
protected void doPost() {
    if (someCondition) {
        sendRedirect();
    }else{
        forward();
    }
}
上文所述是引起该异常的最常见的情况;近日在一个由服务器下载文件函数中避免了上述问题,但仍然出现了此异常。除去冗余部分的函数如下:
public void download(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
          ...
          OutputStream out = response.getOutputStream();
          try {
             ...
             out.write();       
          } catch (Exception e){
               log.error(e.getMessage());
          } finally {
             if (out!=null ){
               out.flush();
               out.close();
          }
          ...
          forward();
   }  
Server端在提交response到Client端之前会向一个缓冲区写入相应头和状态码,然后将内容清空。而一旦缓冲区被清空就标志着该response已被提交(response is committed)。于是在finally块中随着out.flush(),该response已被提交了。原因找到,解决方法同上。

您可能感兴趣的与本文相关的镜像

Stable-Diffusion-3.5

Stable-Diffusion-3.5

图片生成
Stable-Diffusion

Stable Diffusion 3.5 (SD 3.5) 是由 Stability AI 推出的新一代文本到图像生成模型,相比 3.0 版本,它提升了图像质量、运行速度和硬件效率

package com.example.maingateway.devices; import com.example.maingateway.fun.StreamDataCallback; import com.example.maingateway.grpc.StreamDataResponse; import io.grpc.*; import io.grpc.stub.ServerCallStreamObserver; import io.grpc.stub.StreamObserver; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; /** * 统一视频流处理器,接收来自不同厂商的视频流,进行转码和转发 */ @Component @Slf4j public class VideoProcessor { // 设备服务映射 private final Map<String, CameraDeviceService> clientMap = new ConcurrentHashMap<>(); // 流观察者映射(存储更详细的状态信息) private final Map<String, StreamContext> streamContextMap = new ConcurrentHashMap<>(); // 处理异步任务的线程池(建议使用固定大小而非缓存线程池,避免线程爆炸) private final ExecutorService executor = Executors.newFixedThreadPool( Runtime.getRuntime().availableProcessors() * 2); // 启动设备并处理视频流 public void startStream(String deviceId, DeviceParam param, int channel, StreamDataCallback streamDataCallback) { // 避免重复启动 if (clientMap.containsKey(deviceId)) { log.info("设备:{}已在取流中", deviceId); return; } // 1. 通过工厂创建设备实例(海康/大华/其他) CameraDeviceService deviceService = DeviceFactory.createDeviceService(param); clientMap.put(deviceId, deviceService); // 2. 登录设备 String userId = deviceService.login(param); if (userId == null) { log.error("设备登录失败:{}", param.getIp()); clientMap.remove(deviceId); // 清理失败的设备 return; } // 3. 获取实时流并处理 deviceService.getRealTimeStream(userId, channel, streamData -> { // 异步处理,避免阻塞视频流回调 executor.execute(() -> { try { streamDataCallback.onStreamReceived(streamData); pushToMain(deviceId, streamData); } catch (Exception e) { log.error("设备{}流处理异常", deviceId, e); } }); }); } /** * 停止设备取流 */ public void stopStream(String deviceKey) { // 双重检查并原子性移除设备服务 CameraDeviceService cameraDeviceService = clientMap.remove(deviceKey); if (cameraDeviceService != null) { try { cameraDeviceService.logout(deviceKey); } catch (Exception e) { log.warn("设备{}登出失败", deviceKey, e); } } // 清理流上下文 StreamContext context = streamContextMap.remove(deviceKey); if (context != null) { context.close("主动停止取流"); } log.info("已停止设备取流: {}", deviceKey); } /** * 通过gRPC将原始流推给Main */ private void pushToMain(String deviceKey, byte[] rawStream) { // 快速检查:流上下文不存在则直接返回 StreamContext context = streamContextMap.get(deviceKey); if (context == null || !context.isActive()) { log.warn("设备{}无活跃流,跳过推送(数据大小: {}字节)", deviceKey, rawStream.length); return; } try { // 构建流响应(带时间戳) StreamDataResponse response = StreamDataResponse.newBuilder() .setDeviceKey(deviceKey) .setRawStream(com.google.protobuf.ByteString.copyFrom(rawStream)) .setTimestamp(System.currentTimeMillis()) .build(); // 推送流数据(非阻塞) context.observer.onNext(response); } catch (IllegalStateException e) { // 捕获连接已关闭或头部重复发送的异常 log.error("设备{}推流失败:连接状态异常", deviceKey, e); context.close("连接状态异常: " + e.getMessage()); streamContextMap.remove(deviceKey); } catch (StatusRuntimeException e) { // 捕获gRPC状态异常(如CANCELLED) log.error("设备{}gRPC状态异常:{}", deviceKey, e.getStatus().getDescription(), e); context.close("gRPC错误: " + e.getStatus().getDescription()); streamContextMap.remove(deviceKey); } catch (Exception e) { // 捕获其他异常 log.error("设备{}推流发生未知错误", deviceKey, e); context.close("推送异常: " + e.getMessage()); streamContextMap.remove(deviceKey); } } /** * 检查设备是否正在取流 */ public boolean isStreaming(String deviceKey) { StreamContext context = streamContextMap.get(deviceKey); return context != null && context.isActive(); } /** * 取消注册流观察者 */ public void unregisterStreamObserver(String deviceKey) { StreamContext context = streamContextMap.remove(deviceKey); if (context != null) { context.close("Main取消订阅"); log.info("Main已取消订阅设备流: {}", deviceKey); } } /** * 注册流观察者(Main订阅时调用) */ public void registerStreamObserver(String deviceKey, StreamObserver<StreamDataResponse> observer) { // 先移除旧的观察者 StreamContext oldContext = streamContextMap.remove(deviceKey); if (oldContext != null) { oldContext.close("新订阅替代旧订阅"); } // 创建新的流上下文 StreamContext newContext = new StreamContext(deviceKey, observer); streamContextMap.put(deviceKey, newContext); log.info("Main已订阅设备流: {}", deviceKey); } /** * 关闭资源 */ public void shutdown() { // 优雅关闭线程池 executor.shutdown(); try { if (!executor.awaitTermination(5, java.util.concurrent.TimeUnit.SECONDS)) { executor.shutdownNow(); } } catch (InterruptedException e) { executor.shutdownNow(); } // 清理所有流 streamContextMap.forEach((key, context) -> context.close("服务关闭")); streamContextMap.clear(); clientMap.clear(); } /** * 流上下文:管理单个gRPC流的完整状态 */ private static class StreamContext { final String deviceKey; final StreamObserver<StreamDataResponse> observer; final ServerCallStreamObserver<StreamDataResponse> serverCall; final AtomicBoolean isActive = new AtomicBoolean(true); final AtomicBoolean isClosed = new AtomicBoolean(false); StreamContext(String deviceKey, StreamObserver<StreamDataResponse> observer) { this.deviceKey = deviceKey; this.observer = observer; this.serverCall = (observer instanceof ServerCallStreamObserver) ? (ServerCallStreamObserver<StreamDataResponse>) observer : null; // 注册取消处理器:客户端取消时及时清理 if (this.serverCall != null) { this.serverCall.setOnCancelHandler(() -> { close("客户端主动取消"); }); } } /** * 检查流是否处于活跃状态 */ boolean isActive() { return isActive.get() && !isClosed.get() && (serverCall == null || !serverCall.isCancelled()); } /** * 关闭流并清理资源 */ void close(String reason) { if (isClosed.compareAndSet(false, true)) { isActive.set(false); log.info("设备{}流关闭,原因: {}", deviceKey, reason); try { // 仅在未取消的情况下发送完成信号 if (serverCall == null || !serverCall.isCancelled()) { observer.onCompleted(); } } catch (Exception e) { log.warn("设备{}流关闭时发生异常", deviceKey, e); } } } } } 出现Main已订阅设备流: 090124480097 2025-09-17 00:11:25.791 [grpc-nio-worker-ELG-3-1] INFO i.g.n.s.i.g.netty.NettyServerTransport.connections - Transport failed java.lang.IllegalStateException: Stream 5 sent too many headers EOS: false at io.grpc.netty.shaded.io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder.validateHeadersSentState(DefaultHttp2ConnectionEncoder.java:158) at io.grpc.netty.shaded.io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder.writeHeaders0(DefaultHttp2ConnectionEncoder.java:231)的原因,以及解决方法,不要考虑现有架构,你可以提供最优的性能和多线程架构
09-18
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值