以下与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 的配置,其中还包括超时配置。