以下是与
connectTimeout
设置相关的内容。
情况 - 未知主机
如果您有一个无法访问的主机(例如:
http://blablablabla/v1/timeout
),则会尽快收到
UnknownHostException
。主机使用
InetAddress.getByName(<host_name>)
解析,
AbstractPlainSocketImpl::connect()::!addr.isUnresolved()::throw UnknownHostException
没有任何超时。
情况 - 未知端口
如果您有一个可达的主机但无法进行连接,则尽快收到
ConnectException
-
Connection refused: connect
。似乎这发生在本地方法
DualStackPlainSocketImpl::static native void waitForConnect(int fd, int timeout) throws IOException
中,该方法由
DualStackPlainSocketImpl::socketConnect()
调用。超时未得到尊重。
代理?如果使用代理,情况可能会发生变化。如果有可达代理,则可能会出现超时。
相关问题 检查 this answer 是否与您遇到的情况有关。
DNS 轮询 如果将同一域名映射到多个 IP 地址,则客户端将连接到每个 IP,直到找到一个可用的。因此,connectTimeout()
将为列表中不起作用的每个 IP 添加其自己的惩罚。阅读 this article 以了解更多信息。
结论 如果您想获得 connectTimeout
,则可能需要实现自己的重试逻辑或使用代理。
测试connectTimeout
,您可以参考各种方法的答案,以获得防止套接字连接完成从而获取超时的端点。选择解决方案后,您可以在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);
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@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
的设置都将被替换。因此,在超时测试中使用真实应用程序可能是唯一的选择。
注意:还要检查
this article有关
RestTemplate
配置的文章,其中包括超时配置。
connectTimeout
时间内无法建立连接;2)服务器已经关闭,因此无法访问。对于前者,connectTimeout
应该可以解决问题,对于后者,这样做没有意义,因为您的网络客户端已经知道它是不可达的,等待connectTimeout
告诉您这一点是没有意义的。 - Edwin Dalorzo