Spring Boot RestClient的spring.http.client.read-timeout属性为何上限仅30秒,设置更高值会触发空闲超时?
Spring Boot RestClient的spring.http.client.read-timeout属性为何上限仅30秒,设置更高值会触发空闲超时?
这个问题其实和Spring Boot默认使用的Jetty HTTP客户端的双重超时机制直接相关,咱们一步步拆解清楚:
Jetty的两个独立超时配置是核心原因
Jetty HTTP客户端默认维护着两个互不干扰的超时规则:- 总请求超时(Total Request Timeout):控制从请求发起开始,到完整接收完响应的总时长,这正是
spring.http.client.read-timeout属性对应的配置项。 - 空闲超时(Idle Timeout):控制连接在没有任何数据传输(不管是请求体发送还是响应体接收)的情况下,允许保持空闲的最长时间。这个值Jetty默认是30秒,而且Spring Boot并没有提供对应的全局配置属性来直接修改它。
- 总请求超时(Total Request Timeout):控制从请求发起开始,到完整接收完响应的总时长,这正是
两种异常场景的触发逻辑
- 当你把
spring.http.client.read-timeout设为30s时:总请求超时和Jetty默认空闲超时都是30s,此时服务器延迟50秒响应,总请求超时会先触发,所以你看到的是「Total timeout 30000 ms elapsed」的异常。 - 当你把总超时设为60s时:总请求超时是60s,但空闲超时还是默认的30s。你的stub服务器延迟50秒才返回数据,意味着请求发出去后,连接在30秒内完全没有数据传输,这时候Jetty的空闲超时就会先触发,抛出「Idle timeout expired」的异常,直接“截胡”了你的总超时设置。
- 当你把
为什么编程创建
SimpleClientHttpRequestFactory就没问题?
因为SimpleClientHttpRequestFactory默认使用的是JDK自带的HttpURLConnection,它的超时模型更简单——只有连接超时和读取超时(对应总请求时长),没有单独的空闲超时配置。所以你设置的读取超时会直接作为总请求时长生效,不会有额外的空闲超时干扰。如果想通过配置解决,该怎么做?
你可以通过编程方式定制Jetty客户端,让空闲超时和总请求超时保持一致,避免被截胡。示例代码如下:@Bean public RestClient restClient(RestClient.Builder builder, @Value("${spring.http.client.read-timeout}") Duration readTimeout) { JettyClientHttpRequestFactory jettyFactory = new JettyClientHttpRequestFactory(); jettyFactory.setHttpClient(customJettyHttpClient(readTimeout)); return builder.requestFactory(jettyFactory).build(); } private HttpClient customJettyHttpClient(Duration totalTimeout) { HttpClient httpClient = new HttpClient(); // 同步设置总请求超时和空闲超时,避免冲突 httpClient.setRequestTimeout(totalTimeout); httpClient.setIdleTimeout(totalTimeout); return httpClient; }
内容来源于stack exchange




