web-dev-qa-db-fra.com

Le délai d'expiration de la connexion Spring RestTemplate ne fonctionne pas

J'essaie de configurer le délai d'expiration lors d'un appel de service Web externe. J'appelle un service Web externe par Spring Rest Template dans mon service.

À des fins de test de délai de connexion, le service Web externe est arrêté et le serveur d'applications est arrêté.

J'ai configuré 10 secondes pour le délai d'attente, mais malheureusement, je reçois une exception de connexion refusée après une seconde.

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

Veuillez corriger ma compréhension, le cas échéant.

7

Ce qui suit est lié au paramètre connectTimeout.

Cas - Hôte inconnu

Si vous avez un hôte qui n'est pas accessible (par exemple: http://blablablabla/v1/timeout), Vous recevrez UnknownHostException dès que possible. AbstractPlainSocketImpl :: connect() :: !addr.isUnresolved() :: throw UnknownHostException sans aucun timeout. L'hôte est résolu à l'aide de InetAddress.getByName(<Host_name>).

Cas - port inconnu

Si vous avez un hôte accessible mais qu'aucune connexion ne peut être établie, vous recevez ConnectException - Connection refused: connect Dès que possible. Il semble que cela se produise dans une méthode native DualStackPlainSocketImpl :: static native void waitForConnect(int fd, int timeout) throws IOException qui est appelée depuis DualStackPlainSocketImpl :: socketConnect(). Le délai d'attente n'est pas respecté.

Proxy? si un proxy est utilisé, les choses peuvent changer. Ayant un proxy accessible, vous pouvez obtenir le délai d'expiration.

Questions connexes cochez cette réponse comme cela est lié au cas que vous rencontrez.

Round-Robin DNS ayant le même domaine mappé à plusieurs adresses IP obligera le client à se connecter à chacune des adresses IP jusqu'à ce qu'une soit trouvée. Par conséquent, la connectTimeout() ajoutera sa propre pénalité pour chacune des adresses IP de la liste qui ne fonctionnent pas. Lisez cet article pour en savoir plus.

Conclusion si vous souhaitez obtenir le connectTimeout, vous devrez peut-être implémenter votre propre logique de nouvelle tentative ou utiliser un proxy.

Test connectTimeout vous pouvez vous référer à cette réponse de différentes façons d'avoir un point de terminaison qui empêche une connexion socket de terminer ainsi obtenir un délai d'attente. En choisissant une solution, vous pouvez créer un test d'intégration dans Spring-Boot qui valide votre implémentation. Cela peut être similaire au prochain test utilisé pour tester le readTimeout, juste que dans ce cas, l'URL peut être changée en une URL qui empêche une connexion socket.

Test readTimeout

Afin de tester le readTimeout, il doit d'abord y avoir une connexion, donc le service doit être en place. Ensuite, un point d'extrémité peut être fourni qui, pour chaque demande, renvoie une réponse avec un retard important.

Les opérations suivantes peuvent être effectuées dans Spring-Boot afin de créer un test d'intégration:

1. Créez le test

@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. Configurer 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. Application Spring Boot qui sera exécutée au démarrage du test

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

Autres façons de configurer le RestTemplate

Configurer RestTemplate existant

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

Configurer un nouveau RestTemplate à l'aide de RequestConfig

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

Pourquoi ne pas se moquer en utilisant MockRestServiceServer avec un RestTemplate, remplace la fabrique de requêtes. Par conséquent, tous les paramètres RestTemplate seront remplacés. Par conséquent, l'utilisation d'une véritable application pour les tests de temporisation peut être la seule option ici.

Remarque: vérifiez également cet article à propos de la configuration RestTemplate qui inclut également la configuration du délai d'expiration.

25
andreim