Spring流式REST端点报AsyncRequestNotUsableException求助
我实现了一个基于Spring的流式REST端点,控制器和Service层代码如下,原本运行正常,但流式传输一段时间后,服务器抛出AsyncRequestNotUsableException: Response not usable after async request completion,客户端则报Premature EOF错误。
控制器代码
@GetMapping("/stream") public ResponseEntity<StreamingResponseBody> streamData() { Stream<Data> data = service.streamData(); StreamingResponseBody responseBody = httpResponseOutputStream -> { try (Writer writer = new BufferedWriter(new OutputStreamWriter(httpResponseOutputStream))) { data.forEach(d -> { try { writer.write(gson.toJson(d)); writer.flush(); } catch (IOException exception) { logger.error("exception occurred while writing object to stream", exception); } }); } catch (Exception exception) { logger.error("exception occurred while publishing data", exception); } logger.info("finished streaming records"); }; return ResponseEntity.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(responseBody); }
Service层代码
public Stream<Data> streamData() { jdbcTemplate.setFetchSize(500); String q = "SELECT ....."; return this.jdbcTemplate.queryForStream(q, (resultSet, rowNum) -> { Data d = transform(resultSet); return d; } ); }
以下是排查思路和解决方案:
JDBC Stream资源未正确关闭
jdbcTemplate.queryForStream返回的Stream绑定了ResultSet和数据库连接,若未正确关闭,迭代中出现异常或提前结束时,会导致连接/ResultSet未释放,进而触发异步请求异常。
解决:用try-with-resources包裹Stream,确保资源自动回收:StreamingResponseBody responseBody = httpResponseOutputStream -> { try (Writer writer = new BufferedWriter(new OutputStreamWriter(httpResponseOutputStream)); Stream<Data> data = service.streamData()) { // 将Stream放入try-with-resources data.forEach(d -> { try { writer.write(gson.toJson(d)); writer.flush(); } catch (IOException exception) { logger.error("写入对象到流时出错", exception); } }); } catch (Exception exception) { logger.error("发布数据时出错", exception); } logger.info("完成流式记录传输"); };异步请求超时触发终止
Spring MVC默认的异步请求超时时间较短,若流式传输耗时超过阈值,容器会主动终止异步请求,标记Response为已完成,后续写入会抛出AsyncRequestNotUsableException。
解决:调整异步超时配置,比如在application.properties中设置:spring.mvc.async.request-timeout=3600000 # 设置为1小时,根据实际业务调整或者通过配置类全局设置:
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void configureAsyncSupport(AsyncSupportConfigurer configurer) { configurer.setDefaultTimeout(3600000); } }连接超时导致提前断开
客户端或反向代理(如Nginx)的连接/读取超时设置过短,长时间无数据传输时会主动断开连接,服务器端写入时发现连接已关闭,触发异常,客户端则收到Premature EOF。
解决:- 调整客户端RestTemplate的超时配置:
RestTemplate restTemplate = new RestTemplate(); SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setConnectTimeout(30000); // 连接超时30秒 factory.setReadTimeout(3600000); // 读取超时1小时 restTemplate.setRequestFactory(factory); - 若使用反向代理,比如Nginx,调整
proxy_read_timeout参数为合适的值。
- 调整客户端RestTemplate的超时配置:
吞吃异常导致无效写入
当前代码中在forEach的try-catch仅打印日志,未终止流式处理,某条数据写入异常后,后续写入仍会尝试,但此时Response可能已不可用,最终引发更严重的异常。
解决:捕获IOException后终止处理,改用Iterator遍历以便主动中断:try (Writer writer = new BufferedWriter(new OutputStreamWriter(httpResponseOutputStream)); Stream<Data> data = service.streamData()) { Iterator<Data> iterator = data.iterator(); while (iterator.hasNext()) { Data d = iterator.next(); try { writer.write(gson.toJson(d)); writer.flush(); } catch (IOException e) { logger.error("写入数据失败", e); throw new RuntimeException(e); // 抛出异常终止处理,避免无效写入 } } } catch (Exception e) { logger.error("流式传输失败", e); }数据库连接池配置问题
连接池的maxLifetime等参数设置过短,会导致流式处理过程中数据库连接被提前回收,ResultSet关闭后引发Stream处理异常,进而影响异步请求。
解决:检查连接池配置(如HikariCP的maxLifetime、connectionTimeout),确保连接在流式传输期间不会被回收。
内容的提问来源于stack exchange,提问作者Mandroid

