Spring RestTemplate连接超时不起作用

Eas*_*Ans 7 java spring httpconnection resttemplate

我正在尝试配置外部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();
}
Run Code Online (Sandbox Code Playgroud)

请更正我的理解.

and*_*eim 25

以下与connectTimeout设置有关.

案例 - 未知主持人

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

案例 - 未知端口

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

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

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

具有映射到多个IP地址的相同域的DNS Round-Robin将导致客户端连接到每个IP,直到找到一个IP.因此,connectTimeout()将为列表中不起作用的每个IP添加自己的惩罚.阅读本文了解更多信息.

结论如果你想获得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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

配置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;
    }
}
Run Code Online (Sandbox Code Playgroud)

使用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

为什么不嘲讽使用MockRestServiceServerRestTemplate,代替的要求工厂.因此,RestTemplate将替换任何设置.因此,使用真实应用程序进行超时测试可能是唯一的选择.

注意:查看本文关于RestTemplate配置的内容,其中还包括超时配置.