Spring RestTemplate 连接超时不起作用
Posted
技术标签:
【中文标题】Spring RestTemplate 连接超时不起作用【英文标题】:Spring RestTemplate Connection Timeout is not working 【发布时间】:2017-10-10 02:10:25 【问题描述】:我正在尝试配置外部 Web 服务调用时的超时。我在我的服务中通过 Spring Rest Template 调用外部 Web 服务。
出于连接超时测试的目的,外部 Web 服务已停止,应用服务器已关闭。
我已经为超时配置了 10 秒,但不幸的是,我在一秒钟后收到连接被拒绝异常。
try
final RestTemplate restTemplate = new RestTemplate();
((org.springframework.http.client.SimpleClientHttpRequestFactory)
restTemplate.getRequestFactory()).setReadTimeout(1000*10);
((org.springframework.http.client.SimpleClientHttpRequestFactory)
restTemplate.getRequestFactory()).setConnectTimeout(1000*10);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<String>(reqJSON, headers);
ResponseEntity<String> response = restTemplate.exchange(wsURI, HttpMethod.POST, entity, String.class);
String premiumRespJSONStr = response.getBody();
如果有,请纠正我的理解。
【问题讨论】:
你能在不配置超时的情况下连接吗? @ Vaibs,没有。出于测试目的,我没有启动外部 Web 服务。指定的超时不起作用。 @Vaibs,当外部服务启动时,setReadTimeout 正在工作。当外部服务关闭时,setConnectTimeout 不起作用。也就是说,在一秒钟后出现异常,它不会等待我配置的 10 秒。 如果你的服务宕机了为什么要休息模板轮询 10 秒。它会直接抛出一些异常/错误 @Easy2DownVoteHard2Ans 有两种情况:1) 远程服务器已启动,但获得连接的时间比connectTimeout
长;2) 服务器已关闭,因此无法访问。对于前者,connectTimeout
应该可以工作,对于后者,它没有意义,因为您的网络客户端已经知道无法访问,并且让您等待 comnectTimeout
告诉您是没有意义的。
【参考方案1】:
以下与connectTimeout
设置有关。
案例 - 未知主机
如果您有一个无法访问的主机(例如:http://blablablabla/v1/timeout
),那么您将尽快收到UnknownHostException
。 AbstractPlainSocketImpl :: connect() :: !addr.isUnresolved() :: throw UnknownHostException
没有任何超时。使用InetAddress.getByName(<host_name>)
解析主机。
案例 - 未知端口
如果您的主机可访问但无法进行连接,那么您会尽快收到ConnectException
- Connection refused: connect
。这似乎发生在从DualStackPlainSocketImpl :: socketConnect()
调用的本机方法DualStackPlainSocketImpl :: static native void waitForConnect(int fd, int timeout) throws IOException
中。不遵守超时。
代理?如果使用代理,情况可能会发生变化。拥有可访问的代理可能会超时。
相关问题请查看this answer,与您遇到的案例相关。
DNS Round-Robin 将相同的域映射到多个 IP 地址将导致客户端连接到每个 IP,直到找到一个。因此,connectTimeout()
将为列表中每个不起作用的 IP 添加自己的惩罚。阅读this article了解更多信息。
结论如果您想获得connectTimeout
,那么您可能需要实现自己的重试逻辑或使用代理。
测试connectTimeout
您可以参考this answer 的各种方法来阻止套接字连接完成从而获得超时的端点。选择一个解决方案,您可以在 spring-boot 中创建一个集成测试来验证您的实现。这可能类似于用于测试readTimeout
的下一个测试,只是在这种情况下,可以将 URL 更改为阻止套接字连接的 URL。
测试readTimeout
为了测试readTimeout
,首先需要建立连接,因此服务需要启动。然后可以提供一个端点,该端点对于每个请求都返回一个延迟较大的响应。
为了创建集成测试,可以在 spring-boot 中完成以下操作:
1.创建测试
@RunWith(SpringRunner.class)
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = RestTemplateTimeoutConfig.class, RestTemplateTimeoutApplication.class
)
public class RestTemplateTimeoutTests
@Autowired
private RestOperations restTemplate;
@LocalServerPort
private int port;
@Test
public void resttemplate_when_path_exists_and_the_request_takes_too_long_throws_exception()
System.out.format("%s - %s\n", Thread.currentThread().getName(), Thread.currentThread().getId());
Throwable throwable = catchThrowable(() ->
restTemplate.getForEntity(String.format("http://localhost:%d/v1/timeout", port), String.class));
assertThat(throwable).isInstanceOf(ResourceAccessException.class);
assertThat(throwable).hasCauseInstanceOf(SocketTimeoutException.class);
2。配置 RestTemplate
@Configuration
public class RestTemplateTimeoutConfig
private final int TIMEOUT = (int) TimeUnit.SECONDS.toMillis(10);
@Bean
public RestTemplate restTemplate()
return new RestTemplate(getRequestFactory());
private ClientHttpRequestFactory getRequestFactory()
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory();
factory.setReadTimeout(TIMEOUT);
factory.setConnectTimeout(TIMEOUT);
factory.setConnectionRequestTimeout(TIMEOUT);
return factory;
3.测试开始时将运行的 Spring Boot 应用
@SpringBootApplication
@Controller
@RequestMapping("/v1/timeout")
public class RestTemplateTimeoutApplication
public static void main(String[] args)
SpringApplication.run(RestTemplateTimeoutApplication.class, args);
@GetMapping()
public @ResponseStatus(HttpStatus.NO_CONTENT) void getDelayedResponse() throws InterruptedException
System.out.format("Controller thread = %s - %s\n", Thread.currentThread().getName(), Thread.currentThread().getId());
Thread.sleep(20000);
配置 RestTemplate 的替代方法
配置现有的 RestTemplate
@Configuration
public class RestTemplateTimeoutConfig
private final int TIMEOUT = (int) TimeUnit.SECONDS.toMillis(10);
// consider that this is the existing RestTemplate
@Bean
public RestTemplate restTemplate()
return new RestTemplate();
// this will change the RestTemplate settings and create another bean
@Bean
@Primary
public RestTemplate newRestTemplate(RestTemplate restTemplate)
SimpleClientHttpRequestFactory factory =
(SimpleClientHttpRequestFactory) restTemplate.getRequestFactory();
factory.setReadTimeout(TIMEOUT);
factory.setConnectTimeout(TIMEOUT);
return restTemplate;
使用 RequestConfig 配置新的 RestTemplate
@Configuration
public class RestTemplateTimeoutConfig
private final int TIMEOUT = (int) TimeUnit.SECONDS.toMillis(10);
@Bean
public RestTemplate restTemplate()
return new RestTemplate(getRequestFactoryAdvanced());
private ClientHttpRequestFactory getRequestFactoryAdvanced()
RequestConfig config = RequestConfig.custom()
.setSocketTimeout(TIMEOUT)
.setConnectTimeout(TIMEOUT)
.setConnectionRequestTimeout(TIMEOUT)
.build();
CloseableHttpClient client = HttpClientBuilder
.create()
.setDefaultRequestConfig(config)
.build();
return new HttpComponentsClientHttpRequestFactory(client);
为什么不模拟使用 MockRestServiceServer
和 RestTemplate
,替换请求工厂。因此,任何RestTemplate
设置都将被替换。因此,使用真正的应用程序进行超时测试可能是这里唯一的选择。
注意:还要检查this article 关于RestTemplate
的配置,其中还包括超时配置。
【讨论】:
感谢您的出色、包容和详细的回答。这是处理和测试 Spring 连接超时问题的非常有用的指南。 不错的答案。 setSocketTimeout(在上一个 RequestConfig 版本中)是否等同于 setReadTimeout(第一个版本)?以上是关于Spring RestTemplate 连接超时不起作用的主要内容,如果未能解决你的问题,请参考以下文章