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),那么您将尽快收到UnknownHostExceptionAbstractPlainSocketImpl :: connect() :: !addr.isUnresolved() :: throw UnknownHostException 没有任何超时。使用InetAddress.getByName(&lt;host_name&gt;)解析主机。

案例 - 未知端口

如果您的主机可访问但无法进行连接,那么您会尽快收到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);
    

为什么不模拟使用 MockRestServiceServerRestTemplate,替换请求工厂。因此,任何RestTemplate 设置都将被替换。因此,使用真正的应用程序进行超时测试可能是这里唯一的选择。

注意:还要检查this article 关于RestTemplate 的配置,其中还包括超时配置。

【讨论】:

感谢您的出色、包容和详细的回答。这是处理和测试 Spring 连接超时问题的非常有用的指南。 不错的答案。 setSocketTimeout(在上一个 RequestConfig 版本中)是否等同于 setReadTimeout(第一个版本)?

以上是关于Spring RestTemplate 连接超时不起作用的主要内容,如果未能解决你的问题,请参考以下文章

关于spring resttemplate超时设置

Spring Cloud常用组件超时总结

设置RestTemplate的读取超时

resttemplatebuilder 在哪个jar包

Spring boot Resttemplate通过ssh隧道的动态代理设置连接不识别远程DNS

RestTemplate使用HttpClient连接池