Java Spring RestTemplate 连接超时不起作用

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/43909219/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 01:12:53  来源:igfitidea点击:

Spring RestTemplate Connection Timeout is not working

javaspringresttemplatehttpconnection

提问by Easy2DownVoteHard2Ans

I am trying to configure time out when external web service call. I am calling external web service by Spring Rest Template in my service.

我正在尝试配置外部 Web 服务调用时的超时。我在我的服务中通过 Spring Rest Template 调用外部 Web 服务。

For connection timeout testing purpose, the external web service is stopped and application server is down.

出于连接超时测试目的,外部 Web 服务已停止且应用程序服务器已关闭。

I have configured 10 seconds for timeout, but unfortunately i get connection refused exception after a second.

我已经配置了 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();
}

Please correct my understanding if any.

如果有的话请纠正我的理解。

回答by andreim

The following is related to connectTimeoutsetting.

以下与connectTimeout设置有关。

Case - unknown host

案例 - 未知主机

If you have a host that is not reachable (eg: http://blablablabla/v1/timeout) then you will receive UnknownHostExceptionas soon as possible. AbstractPlainSocketImpl :: connect() :: !addr.isUnresolved() :: throw UnknownHostExceptionwithout any timeout. The host is resolved using InetAddress.getByName(<host_name>).

如果您的主机无法访问(例如:),http://blablablabla/v1/timeout那么您将UnknownHostException尽快收到。AbstractPlainSocketImpl :: connect() :: !addr.isUnresolved() :: throw UnknownHostException没有任何超时。主机使用InetAddress.getByName(<host_name>).

Case - unknown port

案例 - 未知端口

If you have a host that is reachable but no connection can be done then you receive ConnectException- Connection refused: connectas soon as possible. It seems that this happens in a native method DualStackPlainSocketImpl :: static native void waitForConnect(int fd, int timeout) throws IOExceptionwhich is called from DualStackPlainSocketImpl :: socketConnect(). The timeout is not respected.

如果你有一台主机可到达的,但没有连接可以做到,那么你收到ConnectException-Connection refused: connect尽快。这似乎发生在DualStackPlainSocketImpl :: static native void waitForConnect(int fd, int timeout) throws IOExceptionDualStackPlainSocketImpl :: socketConnect(). 不遵守超时。

Proxy?if a proxy is used things might change. Having a reachable proxy you might get the timeout.

代理?如果使用代理,事情可能会改变。拥有可访问的代理,您可能会超时。

Related questionscheck this answeras is related to the case you are encountering.

相关问题请检查与您遇到的案例相关的答案

DNS Round-Robinhaving the same domain mapped to multiple IP addresses will cause the client to connect to each of the IPs until one is found. Therefore the connectTimeout()will add its own penalty for each of the IPs in the list which are not working. Read this articlefor more.

具有映射到多个 IP 地址的相同域的DNS 循环将导致客户端连接到每个 IP,直到找到一个。因此,connectTimeout()它将为列表中的每个不工作的 IP 添加自己的惩罚。阅读这篇文章了解更多。

Conclusionif you want to obtain the connectTimeoutthen you might need to implement your own retry logic or use a proxy.

结论如果您想获得 ,connectTimeout那么您可能需要实现自己的重试逻辑或使用代理。

Testing connectTimeoutyou can refer to this answerof various ways of having an endpoint that prevents a socket connection from completing thus obtaining a timeout. Choosing a solution, you can create an integration test in spring-boot which validates your implementation. This can be similar to the next test used for testing the readTimeout, just that for this case the URL can be changed into one that prevents a socket connection.

测试connectTimeout您可以参考此答案,了解具有阻止套接字连接完成从而获得超时的端点的各种方法。选择一个解决方案,您可以在 spring-boot 中创建一个集成测试来验证您的实现。这可以类似于用于测试 的下一个测试readTimeout,只是对于这种情况,可以将 URL 更改为阻止套接字连接的 URL。

Testing readTimeout

测试 readTimeout

In order to test the readTimeoutthere need to be a connection first, therefore the service needs to be up. Then an endpoint can be provided that, for each request, returns a response with a large delay.

为了测试readTimeout需要先建立连接,因此服务需要启动。然后可以提供一个端点,对于每个请求,返回一个具有很大延迟的响应。

The following can be done in spring-boot in order to create an integration test:

为了创建集成测试,可以在 spring-boot 中完成以下操作:

1. Create the test

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. Configure RestTemplate

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 app that will be run when the test starts

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);
    }
}

Alternative ways of configuring the RestTemplate

配置 RestTemplate 的替代方法

Configure existing 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;
    }
}

Configure a new RestTemplate using RequestConfig

使用 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);
    }
}

Why not mockingusing MockRestServiceServerwith a RestTemplate, replaces the request factory. Therefore any RestTemplatesettings will be replaced. Therefore using a real app for timeout testing might be the only option here.

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

Note:check also this articleabout RestTemplateconfiguration which include also the timeout configuration.

注意:还可以查看这篇关于RestTemplate配置的文章,其中还包括超时配置。