差異處
這裏顯示兩個版本的差異處。
java:web:resteasy:invalid_connection_request_timeout [2018/06/02 19:01] tony [Problem] |
java:web:resteasy:invalid_connection_request_timeout [2023/06/25 09:48] |
||
---|---|---|---|
行 1: | 行 1: | ||
- | {{tag>resteasy httpclient}} | ||
- | ====== RESTEasy - Connection Pool的Request Timeout沒有作用 ====== | ||
- | ===== Problem ===== | ||
- | REST API的開發者,大都會非常頻繁的存取REST服務提供者;Connection Pool是其中一個增進存取效率的方式。然而,這陣子在使用RESTEasy + Apache Connection Pool後,發現ConnectionRequestTimeout並沒作用。本篇文章主要分享目前的解決方法。 | ||
- | ===== Version Info ===== | ||
- | 因為相依libraries的限制,我們目前還沒使用最新版本的RESTEasy,資訊如下: | ||
- | <code> | ||
- | resteasy-client: 3.0.14.Final | ||
- | httpcore: 4.3.3 | ||
- | httpclient: 4.3.6 | ||
- | </code> | ||
- | ===== Test RESTClient ===== | ||
- | 我寫了一個測試去確認ConnectionRequestTimeout是否有作用,方法如下: | ||
- | - 將Pool大小設定為1。 | ||
- | - 設定ConnectionRequestTimeout為2秒。 | ||
- | - 依序發起兩個HTTP Get,但第一個請求不關閉。 | ||
- | - 接著發起第二個HTTP Get,由於沒有有效的連線可以使用,會Block。 | ||
- | - 在2秒後,Client會收到Exception,Root Cause為ConnectionPoolTimeoutException。 | ||
- | 首先是HttpEngine設定的部分,由於這個版本ResteasyClientBuilder內建的PoolManager有些問題,因此我是另外產生的,程式碼大致如下: | ||
- | <code bash> | ||
- | // 這是自己寫的Builder,產生的實體是PoolingHttpClientConnectionManager | ||
- | private HttpClientConnectionManager initConnectionManager(int poolSize, int perRoute) { | ||
- | HttpClientConnectionManagerBuilder builder = new HttpClientConnectionManagerBuilder(); | ||
- | return builder.withMaxTotal(poolSize).withDefaultMaxPerRoute(perRoute).build(); | ||
- | } | ||
- | // TestCase的timeout設定為20秒,避免有問題時會卡很久 | ||
- | @Test(timeout=20000) | ||
- | public void testRequestTimeoutWithCustomHttpEngine() throws Exception { | ||
- | int expectTimeout = 2*1000; | ||
- | |||
- | connectionManager = initConnectionManager(20 , 1); | ||
- | |||
- | RequestConfig requestConfig = RequestConfig.custom() | ||
- | .setConnectionRequestTimeout(expectTimeout) | ||
- | .build(); | ||
- | |||
- | ClientHttpEngine httpEngin = new ClientHttpEngineBuilder() | ||
- | .withRequestConfig(requestConfig) | ||
- | .withConnectionManager(connectionManager).build(); | ||
- | |||
- | ResteasyClientBuilder clientBuilder = (ResteasyClientBuilder)ResteasyClientBuilder.newBuilder(); | ||
- | clientBuilder.httpEngine(httpEngin); | ||
- | // 以下略 | ||
- | </code> | ||
- | 接著是發出兩個請求,第一個請求沒執行close,接著執行第二個請求: | ||
- | <code java> | ||
- | // 延續httpEngine的初始化 | ||
- | Client client = clientBuilder.build(); | ||
- | |||
- | Response leak_repsonse = client.target(target).request().get(); | ||
- | Response second_response = null; | ||
- | |||
- | long before = System.currentTimeMillis(); | ||
- | try { | ||
- | second_response = client.target(target).request().get(); | ||
- | fail("should be timeout"); | ||
- | } catch (Exception e) { | ||
- | assertNotNull(e.getCause()); | ||
- | assertTrue(e.getCause() instanceof ConnectionPoolTimeoutException); | ||
- | assertEquals(expectTimeout, System.currentTimeMillis() - before, 500); | ||
- | } finally { | ||
- | leak_repsonse.close(); | ||
- | if( second_response != null ) | ||
- | second_response.close(); | ||
- | } | ||
- | } | ||
- | </code> | ||
- | 在執行以上測試後,會因為第二個請求block直到Test case 20秒timeout而錯誤;此測試並沒發生預期的ConnectionPoolTimeoutException。 | ||
- | ===== Test HttpClient ===== | ||
- | 由於前一個測試沒達到預期效果,因此繼續確認PoolingHttpClientConnectionManager是否有問題。測試流程與前一個測試大同小異,差別只在於是直接對HttpClient做操作: | ||
- | <code java> | ||
- | @Test(timeout=20*1000) | ||
- | public void testRequestTimeout() throws Exception { | ||
- | int expect_timeout = 2*1000; | ||
- | connectionManager = new HttpClientConnectionManagerBuilder().build(); | ||
- | RequestConfig requestConfig = RequestConfig.custom() | ||
- | .setConnectionRequestTimeout(expect_timeout) | ||
- | .build(); | ||
- | |||
- | HttpClient httpClient = HttpClients.custom().setConnectionManager(connectionManager) | ||
- | .setDefaultRequestConfig(requestConfig) | ||
- | .build(); | ||
- | |||
- | HttpUriRequest uriRequest = new HttpGet(target); | ||
- | HttpResponse response = httpClient.execute(uriRequest); | ||
- | assertEquals(200, response.getStatusLine().getStatusCode()); | ||
- | |||
- | long before = System.currentTimeMillis(); | ||
- | try { | ||
- | uriRequest = new HttpGet(target); | ||
- | response = httpClient.execute(uriRequest); | ||
- | } catch( ConnectionPoolTimeoutException e ) { | ||
- | assertEquals("Timeout waiting for connection from pool", e.getMessage()); | ||
- | } | ||
- | long duration = System.currentTimeMillis() - before; | ||
- | assertEquals(expect_timeout, duration, 500); | ||
- | } | ||
- | </code> | ||
- | 這個測試最後會因為第二個連線無法在2秒內取得而拋出ConnectionPoolTimeoutException。 | ||
- | ===== How to fix? ===== | ||
- | 根據這兩個測試,懷疑是RESTEasy與HttpClient整合起來的問題。測試過更新RESTEasy與HttpClient的libraries後,確認只要將HttpClient的library升級至4.5.3以上就可以解決問題: | ||
- | <code> | ||
- | resteasy-client: 3.0.14.Final | ||
- | httpcore: 4.3.3 | ||
- | httpclient: 4.5.3 | ||
- | </code> | ||
- | ===== ===== | ||
- | ---- | ||
- | \\ | ||
- | ~~DISQUS~~ |