設定 Jersey 的連線逾時和讀取逾時
一、簡介
在本教程中,我們將探討在 REST 用戶端中配置連接/讀取逾時的重要性。我們將使用 Jersey(常見的 JAX-RS 實作)來演示這一點。
2. 為什麼要設定連線和讀取逾時?
套接字的超時設定對於響應性和可靠性至關重要的應用程式至關重要。例如,延遲可能會影響用戶體驗或導致金融或電子商務應用程式中的交易失敗。
同樣,分散式系統中錯誤配置的逾時可能會導致級聯延遲和資源瓶頸。選擇適當的超時值可確保我們的應用程式即使在具有挑戰性的網路條件下也保持穩健和回應能力。
3. 依賴關係和基本設置
為了了解超時的工作原理,我們將配置 Jersey 用戶端來呼叫慢速 REST API,並觀察回應時間超過配置的逾時時間時的行為。
3.1.依賴關係
對於客戶端,我們需要jersey-client
來與 API 進行 HTTP 通訊:
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>3.1.9</version>
</dependency>
對於伺服器,我們需要jersey-server
:
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-server</artifactId>
<version>3.1.9</version>
</dependency>
然後,為了在測試中運行伺服器,我們需要jaxrs-ri
、 jersey-container-grizzly2-servlet
和jersey-test-framework-provider-grizzly2
:
<dependency>
<groupId>org.glassfish.jersey.bundles</groupId>
<artifactId>jaxrs-ri</artifactId>
<version>3.1.9</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-grizzly2-servlet</artifactId>
<version>3.1.9</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<version>3.1.9</version>
<scope>test</scope>
</dependency>
3.2. REST API 伺服器
REST API 包含我們將在測試中使用的單一端點。這個操作的實現是無關緊要的,因為我們的目標是模擬延遲響應。我們將引入一些睡眠時間來模擬緩慢的伺服器:
@Path("/timeout")
public class TimeoutResource {
public static final long STALL = TimeUnit.SECONDS.toMillis(2l);
@GET
public String get() throws InterruptedException {
Thread.sleep(STALL);
return "processed";
}
}
稍後,我們將使用STALL
變數來確定逾時值。
3.3.客戶端設定
我們的客戶端只需要端點 URI 和通用get(Client)
方法即可存取它。我們也將TIMEOUT
定義為我們先前定義的STALL
變數的一半,這樣我們就可以強制執行逾時場景:
public class JerseyTimeoutClient {
private static final long TIMEOUT = TimeoutResource.STALL / 2;
private final String endpoint;
public JerseyTimeoutClient(String endpoint) {
this.endpoint = endpoint;
}
private String get(Client client) {
return client.target(endpoint)
.request()
.get(String.class);
}
// ...
}
3.4.測試設定
我們將定義兩個客戶端,一個用於測試讀取逾時,另一個用於測試連線逾時。
讓我們從端點的基底位址開始:
static final URI BASE = URI.create("http://localhost:8082");
為了測試讀取逾時,我們將使用正確的端點。由於我們的逾時是伺服器處理請求所用時間的一半,因此保證會出現SocketTimeoutException
:
static final String CORRECT_ENDPOINT = BASE + "/timeout";
JerseyTimeoutClient readTimeoutClient = new JerseyTimeoutClient(CORRECT_ENDPOINT);
然後,為了測試連線逾時,我們將用無法存取的 IP 位址取代主機:
static final String INCORRECT_ENDPOINT = BASE.toString()
.replace(BASE.getHost(), "10.255.255.1");
JerseyTimeoutClient connectTimeoutClient = new JerseyTimeoutClient(INCORRECT_ENDPOINT);
最後,讓我們新增一個斷言方法以在我們的測試中重複使用。它先捕捉 JAX-RS 拋出的ProcessingException
,然後我們檢查原因,它應該永遠是SocketTimeoutException
。最後,我們檢查訊息以區分連接逾時和讀取逾時:
private void assertTimeout(String message, Executable executable) {
ProcessingException exception = assertThrows(ProcessingException.class, executable);
Throwable cause = exception.getCause();
assertInstanceOf(SocketTimeoutException.class, cause);
assertEquals(message, cause.getMessage());
}
現在我們已準備好進行測試,讓我們看看如何配置逾時。
4.使用ClientBuilder
API
讓我們回到JerseyTimeoutClient
並使用ClientBuilder
添加我們的第一個實作。我們可以為相同客戶端設定兩種逾時配置( connectTimeout()
和readTimeout()
)。我們透過將新建立的客戶端傳遞給我們先前建立的get()
方法來呼叫端點:
public String viaClientBuilder() {
ClientBuilder builder = ClientBuilder.newBuilder()
.connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
.readTimeout(TIMEOUT, TimeUnit.MILLISECONDS);
return get(builder.build());
}
我們來測試一下讀取超時:
@Test
void givenCorrectEndpoint_whenClientBuilderAndSlowServer_thenReadTimeout() {
assertTimeout("Read timed out", readTimeoutClient::viaClientBuilder);
}
現在進行連線逾時測試:
@Test
void givenIncorrectEndpoint_whenClientBuilder_thenConnectTimeout() {
assertTimeout("Connect timed out", connectTimeoutClient::viaClientBuilder);
}
使用此建構器產生的所有用戶端都將兩個逾時設定為所需的值。如果未設置, 預設行為是無限期等待回應,這與將它們設為零相同。
接下來,根據不同的需求,我們探討三種在Client
上設定這些超時的替代方法。
5. 使用ClientConfig
對象
我們也可以透過ClientConfig
配置逾時,並且可以在建置新客戶端時使用它:
public String viaClientConfig() {
ClientConfig config = new ClientConfig();
config.property(ClientProperties.CONNECT_TIMEOUT, TIMEOUT);
config.property(ClientProperties.READ_TIMEOUT, TIMEOUT);
return get(ClientBuilder.newClient(config));
}
如果我們想要將相同的配置套用到許多客戶端,那麼使用ClientConfig
會很有幫助。當我們有許多複雜的動態配置並希望使其可重複使用時,這尤其方便。
6.使用client.property()
方法
我們也可以直接在Client
上設定這些屬性:
public String viaClientProperty() {
Client client = ClientBuilder.newClient();
client.property(ClientProperties.CONNECT_TIMEOUT, TIMEOUT);
client.property(ClientProperties.READ_TIMEOUT, TIMEOUT);
return get(client);
}
這對於 2.1 之前的 Jersey 版本是必要的,因為ClientBuilder
的connectTimeout()
不可用。
7. 設定每個請求的逾時
最後,我們新增一個使用property()
方法設定請求逾時的方法。我們首先重寫get()
方法以包含timeout
參數:
private String get(Client client, Long requestTimeout) {
Builder request = client.target(endpoint).request();
if (requestTimeout != null) {
request.property(ClientProperties.CONNECT_TIMEOUT, requestTimeout);
request.property(ClientProperties.READ_TIMEOUT, requestTimeout);
}
return request.get(String.class);
}
現在,我們可以傳遞所需的請求逾時值:
public String viaRequestProperty() {
return get(ClientBuilder.newClient(), TIMEOUT);
}
我們使用此設定來覆寫特定請求的全域設定或對逾時值有不同的要求。
八、結論
在本文中,我們了解了配置連接和讀取逾時對於建立強大且響應迅速的 REST 用戶端至關重要。透過了解 Jersey 提供的不同方法,例如ClientBuilder
、 ClientConfig
和每個請求配置,我們可以自訂客戶端行為以滿足特定的應用程式需求。
與往常一樣,原始碼可以在 GitHub 上取得。