Configurar o timout de conexão, que por padrão é infinito.

Lembrando que o RestEasy 2 é baseado no JAX-RS 1.1, enquanto o RestEasy 3 é baseado no JAX-RS 2.0. O que veremos aqui se aplica ao RestEasy 2.

Versões no ambiente

  • JBoss EAP 6.4.23
  • RestEasy 2.3.10.Final-redhat-1
  • httpclient-4.3.6.redhat-1

RequestConfig

Primeiro é preciso criar uma instância de RequestConfig contendo os timeouts. São 3 timeouts descritos abaixo. No exemplo são 20 segundos de timeout para cada tipo. Pode criar uma instância por aplicação.

private static final RequestConfig requestConfig = RequestConfig.custom()
    // (http.connection.timeout) - the time to establish the connection with the remote host
    .setConnectTimeout(20 * 1000)
    // (http.connection-manager.timeout) - the time to wait for a connection from the connection manager/pool
    .setConnectionRequestTimeout(20 * 1000)
    // (http.socket.timeout) - the time waiting for data - after establishing the connection; maximum time of inactivity between two data packets
    .setSocketTimeout(20 * 1000)
    .build();

Criar o cliente http

Criar uma instância do cliente http. Pode criar uma instância por aplicação.

private static final CloseableHttpClient httpClient = HttpClientBuilder
    .create()
    .setDefaultRequestConfig(requestConfig)
    .build();

Criar ums intância de ApacheHttpClient4Executor (ClientExecutor)

Criar uma instância de ApacheHttpClient4Executor.

ApacheHttpClient4Executor clientExecutor = new ApacheHttpClient4Executor(httpClient) {
    @Override
    public void loadHttpMethod(ClientRequest request, HttpRequestBase httpMethod) throws Exception {
        httpMethod.setConfig(requestConfig);
        super.loadHttpMethod(request, httpMethod);
    }
};

📋 Mais abaixo será explicado porque criar essa classe anônima. E aqui é o pulo do gato! 😺

Executar a requisição http

Algo como:

RegisterBuiltin.register(ResteasyProviderFactory.getInstance());

// Comentado de propósito, será usado o que foi criado na etapa anterior (ver detalhes mais abaixo)
// ApacheHttpClient4Executor clientExecutor = new ApacheHttpClient4Executor(httpClient);

GoogleRecaptchaV3Client client = ProxyFactory.create(GoogleRecaptchaV3Client.class, base, clientExecutor);

Response response = client.siteverify(...); // executa a requisição http

entity = response.getEntity()

📋 GoogleRecaptchaV3Client é apenas uma interface com o contrato de acesso ao endpoint.

🎉 🎉 Agora o timeout da requisição irá funcionar. Basta acessar um endpoint que demora a responder mais que os timeouts estabelecidos. 🥳 🥳

Porque definir a classe anônima ApacheHttpClient4Executor ?

Foi necessário instânciar a classe anônima ApacheHttpClient4Executor sobrescrevendo o método loadHttpMethod.

O motivo é porque a configuração de RequestConfig (que seta os timeouts), mesmo sendo definida no CloseableHttpClient (como visto acima), ou mesmo definida usando o HttpClientContext como no snippet abaixo,

// ...
HttpClientContext localContext = HttpClientContext.create();
localContext.setRequestConfig(requestConfig);
clientExecutor.setHttpContext(localContext);
// ...

não funciona. Debugando é possível ver que isso ocorre porque no método org.apache.http.impl.client.InternalHttpClient.doExecute(HttpHost, HttpRequest, HttpContext) na linha config = HttpClientParamConfig.getRequestConfig(params); e localcontext.setRequestConfig(config); é definido uma instância de RequestConfig que não é a que definimos.

Quando sobrescrevemos o método loadHttpMethod, a nossa configuração de RequestConfig vai prevalecer devido a linha config = ((Configurable) request).getConfig(); no método org.apache.http.impl.client.InternalHttpClient.doExecute(HttpHost, HttpRequest, HttpContext).

Referências