Spring RestTemplate连接超时不起作用 [英] Spring RestTemplate Connection Timeout is not working

查看:2576
本文介绍了Spring RestTemplate连接超时不起作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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服务已停止并且应用程序服务器已关闭.

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

我已经为超时配置了10秒,但是不幸的是我在一秒钟后收到连接被拒绝的异常.

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

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

请更正我的理解.

推荐答案

以下与connectTimeout设置有关.

案例-未知主机

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

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

案例-未知端口

如果您的主机可访问但无法建立连接,则您将尽快收到ConnectException-Connection refused: connect.看来这发生在从DualStackPlainSocketImpl :: socketConnect()调用的本机方法DualStackPlainSocketImpl :: static native void waitForConnect(int fd, int timeout) throws IOException中.超时不受尊重.

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

代理?(如果使用代理),情况可能会发生变化.拥有可访问的代理服务器,您可能会超时.

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

相关问题,将答案选为与您遇到的情况有关.

Related questions check this answer as is related to the case you are encountering.

DNS循环,将导致客户端连接到每个IP,直到找到一个为止.因此,connectTimeout()将对列表中每个无效的IP添加自己的罚款.阅读这篇文章以了解更多信息.

DNS Round-Robin having 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 article for more.

结论,如果要获取connectTimeout,则可能需要实现自己的重试逻辑或使用代理.

Conclusion if you want to obtain the connectTimeout then you might need to implement your own retry logic or use a proxy.

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

Testing connectTimeout you can refer to this answer of 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.

测试readTimeout

Testing readTimeout

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

In order to test the readTimeout there 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.

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

The following can be done in spring-boot in order to create an integration 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.配置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);
    }
}

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

Why not mocking using MockRestServiceServer with a RestTemplate, replaces the request factory. Therefore any RestTemplate settings will be replaced. Therefore using a real app for timeout testing might be the only option here.

注意:还请本文有关RestTemplate配置的信息其中还包括超时配置.

Note: check also this article about RestTemplate configuration which include also the timeout configuration.

这篇关于Spring RestTemplate连接超时不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆