Skip to content

Commit c2d588d

Browse files
authored
[Java][apache-httpclient] Apply connect timeout to requests, add read timeout support (#24284)
* fix(java/apache-httpclient): apply connect timeout to requests, add read timeout support setConnectTimeout() stored the value in a field that was never read, so the configured timeout was silently ignored and Apache HttpClient's own defaults applied (3-minute connect timeout, unbounded response wait). Wire connectionTimeout into a per-request RequestConfig set on the HttpClientContext in invokeAPI(), and add symmetric getReadTimeout()/ setReadTimeout() support mapped to the response timeout. The per-request approach works with both httpclient5 5.1.x (Gradle template) and 5.2.x (Maven template) and preserves a user-supplied custom CloseableHttpClient instead of rebuilding it. Fixes #24269 * fix(java/apache-httpclient): preserve custom client request-config defaults, reject negative timeouts Address review feedback: - when building the per-request RequestConfig, start from the default request configuration of the underlying HTTP client (via Configurable) so a caller-supplied client's redirect policy, cookie spec and other request defaults are preserved and only the configured timeouts are overridden - reject negative values in setConnectTimeout()/setReadTimeout() with IllegalArgumentException instead of silently ignoring them * test(java/apache-httpclient): close custom HTTP client in test via try-with-resources
1 parent 80c08fa commit c2d588d

5 files changed

Lines changed: 330 additions & 24 deletions

File tree

  • modules/openapi-generator/src/main/resources/Java/libraries/apache-httpclient
  • samples/client

modules/openapi-generator/src/main/resources/Java/libraries/apache-httpclient/ApiClient.mustache

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import org.openapitools.jackson.nullable.JsonNullableJackson3Module;
3131
{{/useJackson3}}
3232
{{/openApiNullable}}
3333

34+
import org.apache.hc.client5.http.config.Configurable;
35+
import org.apache.hc.client5.http.config.RequestConfig;
3436
import org.apache.hc.client5.http.cookie.BasicCookieStore;
3537
import org.apache.hc.client5.http.cookie.Cookie;
3638
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
@@ -53,6 +55,7 @@ import org.apache.hc.core5.http.io.entity.FileEntity;
5355
import org.apache.hc.core5.http.io.entity.StringEntity;
5456
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
5557
import org.apache.hc.core5.http.message.BasicNameValuePair;
58+
import org.apache.hc.core5.util.Timeout;
5659

5760
import java.util.Collection;
5861
import java.util.Collections;
@@ -135,6 +138,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} {
135138
protected Map<String, String> serverVariables = null;
136139
protected boolean debugging = false;
137140
protected int connectionTimeout = 0;
141+
protected int readTimeout = 0;
138142

139143
protected CloseableHttpClient httpClient;
140144
protected ObjectMapper objectMapper;
@@ -555,15 +559,50 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} {
555559

556560
/**
557561
* Set the connect timeout (in milliseconds).
558-
* A value of 0 means no timeout, otherwise values must be between 1 and
559-
* {@link Integer#MAX_VALUE}.
562+
* A value of 0 means the HTTP client's default connect timeout is used,
563+
* otherwise values must be between 1 and {@link Integer#MAX_VALUE}.
564+
* The timeout is applied to each request via its request configuration;
565+
* other request configuration defaults of the underlying HTTP client
566+
* are preserved.
560567
* @param connectionTimeout Connection timeout in milliseconds
561568
* @return API client
569+
* @throws IllegalArgumentException if connectionTimeout is negative
562570
*/
563-
public ApiClient setConnectTimeout(int connectionTimeout) {
564-
this.connectionTimeout = connectionTimeout;
565-
return this;
566-
}
571+
public ApiClient setConnectTimeout(int connectionTimeout) {
572+
if (connectionTimeout < 0) {
573+
throw new IllegalArgumentException("connectionTimeout must not be negative");
574+
}
575+
this.connectionTimeout = connectionTimeout;
576+
return this;
577+
}
578+
579+
/**
580+
* Read timeout (in milliseconds).
581+
* @return Read timeout
582+
*/
583+
public int getReadTimeout() {
584+
return readTimeout;
585+
}
586+
587+
/**
588+
* Set the read timeout (in milliseconds), i.e. the response timeout
589+
* while waiting for data after the connection is established.
590+
* A value of 0 means the HTTP client's default response timeout is used,
591+
* otherwise values must be between 1 and {@link Integer#MAX_VALUE}.
592+
* The timeout is applied to each request via its request configuration;
593+
* other request configuration defaults of the underlying HTTP client
594+
* are preserved.
595+
* @param readTimeout Read timeout in milliseconds
596+
* @return API client
597+
* @throws IllegalArgumentException if readTimeout is negative
598+
*/
599+
public ApiClient setReadTimeout(int readTimeout) {
600+
if (readTimeout < 0) {
601+
throw new IllegalArgumentException("readTimeout must not be negative");
602+
}
603+
this.readTimeout = readTimeout;
604+
return this;
605+
}
567606

568607
/**
569608
* Get the date format used to parse/format date parameters.
@@ -1150,6 +1189,20 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} {
11501189
HttpClientContext context = HttpClientContext.create();
11511190
context.setCookieStore(store);
11521191

1192+
if (connectionTimeout > 0 || readTimeout > 0) {
1193+
// start from the default request configuration of the underlying HTTP client, if
1194+
// accessible, so that only the configured timeouts are overridden
1195+
RequestConfig defaultConfig = httpClient instanceof Configurable ? ((Configurable) httpClient).getConfig() : null;
1196+
RequestConfig.Builder requestConfigBuilder = defaultConfig == null ? RequestConfig.custom() : RequestConfig.copy(defaultConfig);
1197+
if (connectionTimeout > 0) {
1198+
requestConfigBuilder.setConnectTimeout(Timeout.ofMilliseconds(connectionTimeout));
1199+
}
1200+
if (readTimeout > 0) {
1201+
requestConfigBuilder.setResponseTimeout(Timeout.ofMilliseconds(readTimeout));
1202+
}
1203+
context.setRequestConfig(requestConfigBuilder.build());
1204+
}
1205+
11531206
ContentType contentTypeObj = getContentType(contentType);
11541207
if (body != null || !formParams.isEmpty()) {
11551208
if (isBodyAllowed(method)) {

samples/client/echo_api/java/apache-httpclient/src/main/java/org/openapitools/client/ApiClient.java

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import com.fasterxml.jackson.core.JsonProcessingException;
2121
import org.openapitools.jackson.nullable.JsonNullableModule;
2222

23+
import org.apache.hc.client5.http.config.Configurable;
24+
import org.apache.hc.client5.http.config.RequestConfig;
2325
import org.apache.hc.client5.http.cookie.BasicCookieStore;
2426
import org.apache.hc.client5.http.cookie.Cookie;
2527
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
@@ -42,6 +44,7 @@
4244
import org.apache.hc.core5.http.io.entity.StringEntity;
4345
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
4446
import org.apache.hc.core5.http.message.BasicNameValuePair;
47+
import org.apache.hc.core5.util.Timeout;
4548

4649
import java.util.Collection;
4750
import java.util.Collections;
@@ -95,6 +98,7 @@ public class ApiClient extends JavaTimeFormatter {
9598
protected Map<String, String> serverVariables = null;
9699
protected boolean debugging = false;
97100
protected int connectionTimeout = 0;
101+
protected int readTimeout = 0;
98102

99103
protected CloseableHttpClient httpClient;
100104
protected ObjectMapper objectMapper;
@@ -430,15 +434,50 @@ public int getConnectTimeout() {
430434

431435
/**
432436
* Set the connect timeout (in milliseconds).
433-
* A value of 0 means no timeout, otherwise values must be between 1 and
434-
* {@link Integer#MAX_VALUE}.
437+
* A value of 0 means the HTTP client's default connect timeout is used,
438+
* otherwise values must be between 1 and {@link Integer#MAX_VALUE}.
439+
* The timeout is applied to each request via its request configuration;
440+
* other request configuration defaults of the underlying HTTP client
441+
* are preserved.
435442
* @param connectionTimeout Connection timeout in milliseconds
436443
* @return API client
444+
* @throws IllegalArgumentException if connectionTimeout is negative
437445
*/
438-
public ApiClient setConnectTimeout(int connectionTimeout) {
439-
this.connectionTimeout = connectionTimeout;
440-
return this;
441-
}
446+
public ApiClient setConnectTimeout(int connectionTimeout) {
447+
if (connectionTimeout < 0) {
448+
throw new IllegalArgumentException("connectionTimeout must not be negative");
449+
}
450+
this.connectionTimeout = connectionTimeout;
451+
return this;
452+
}
453+
454+
/**
455+
* Read timeout (in milliseconds).
456+
* @return Read timeout
457+
*/
458+
public int getReadTimeout() {
459+
return readTimeout;
460+
}
461+
462+
/**
463+
* Set the read timeout (in milliseconds), i.e. the response timeout
464+
* while waiting for data after the connection is established.
465+
* A value of 0 means the HTTP client's default response timeout is used,
466+
* otherwise values must be between 1 and {@link Integer#MAX_VALUE}.
467+
* The timeout is applied to each request via its request configuration;
468+
* other request configuration defaults of the underlying HTTP client
469+
* are preserved.
470+
* @param readTimeout Read timeout in milliseconds
471+
* @return API client
472+
* @throws IllegalArgumentException if readTimeout is negative
473+
*/
474+
public ApiClient setReadTimeout(int readTimeout) {
475+
if (readTimeout < 0) {
476+
throw new IllegalArgumentException("readTimeout must not be negative");
477+
}
478+
this.readTimeout = readTimeout;
479+
return this;
480+
}
442481

443482
/**
444483
* Get the date format used to parse/format date parameters.
@@ -1016,6 +1055,20 @@ public <T> T invokeAPI(
10161055
HttpClientContext context = HttpClientContext.create();
10171056
context.setCookieStore(store);
10181057

1058+
if (connectionTimeout > 0 || readTimeout > 0) {
1059+
// start from the default request configuration of the underlying HTTP client, if
1060+
// accessible, so that only the configured timeouts are overridden
1061+
RequestConfig defaultConfig = httpClient instanceof Configurable ? ((Configurable) httpClient).getConfig() : null;
1062+
RequestConfig.Builder requestConfigBuilder = defaultConfig == null ? RequestConfig.custom() : RequestConfig.copy(defaultConfig);
1063+
if (connectionTimeout > 0) {
1064+
requestConfigBuilder.setConnectTimeout(Timeout.ofMilliseconds(connectionTimeout));
1065+
}
1066+
if (readTimeout > 0) {
1067+
requestConfigBuilder.setResponseTimeout(Timeout.ofMilliseconds(readTimeout));
1068+
}
1069+
context.setRequestConfig(requestConfigBuilder.build());
1070+
}
1071+
10191072
ContentType contentTypeObj = getContentType(contentType);
10201073
if (body != null || !formParams.isEmpty()) {
10211074
if (isBodyAllowed(method)) {

samples/client/petstore/java/apache-httpclient-jackson3/src/main/java/org/openapitools/client/ApiClient.java

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import tools.jackson.core.JacksonException;
2323
import org.openapitools.jackson.nullable.JsonNullableJackson3Module;
2424

25+
import org.apache.hc.client5.http.config.Configurable;
26+
import org.apache.hc.client5.http.config.RequestConfig;
2527
import org.apache.hc.client5.http.cookie.BasicCookieStore;
2628
import org.apache.hc.client5.http.cookie.Cookie;
2729
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
@@ -44,6 +46,7 @@
4446
import org.apache.hc.core5.http.io.entity.StringEntity;
4547
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
4648
import org.apache.hc.core5.http.message.BasicNameValuePair;
49+
import org.apache.hc.core5.util.Timeout;
4750

4851
import java.util.Collection;
4952
import java.util.Collections;
@@ -142,6 +145,7 @@ public class ApiClient extends JavaTimeFormatter {
142145
protected Map<String, String> serverVariables = null;
143146
protected boolean debugging = false;
144147
protected int connectionTimeout = 0;
148+
protected int readTimeout = 0;
145149

146150
protected CloseableHttpClient httpClient;
147151
protected ObjectMapper objectMapper;
@@ -525,15 +529,50 @@ public int getConnectTimeout() {
525529

526530
/**
527531
* Set the connect timeout (in milliseconds).
528-
* A value of 0 means no timeout, otherwise values must be between 1 and
529-
* {@link Integer#MAX_VALUE}.
532+
* A value of 0 means the HTTP client's default connect timeout is used,
533+
* otherwise values must be between 1 and {@link Integer#MAX_VALUE}.
534+
* The timeout is applied to each request via its request configuration;
535+
* other request configuration defaults of the underlying HTTP client
536+
* are preserved.
530537
* @param connectionTimeout Connection timeout in milliseconds
531538
* @return API client
539+
* @throws IllegalArgumentException if connectionTimeout is negative
532540
*/
533-
public ApiClient setConnectTimeout(int connectionTimeout) {
534-
this.connectionTimeout = connectionTimeout;
535-
return this;
536-
}
541+
public ApiClient setConnectTimeout(int connectionTimeout) {
542+
if (connectionTimeout < 0) {
543+
throw new IllegalArgumentException("connectionTimeout must not be negative");
544+
}
545+
this.connectionTimeout = connectionTimeout;
546+
return this;
547+
}
548+
549+
/**
550+
* Read timeout (in milliseconds).
551+
* @return Read timeout
552+
*/
553+
public int getReadTimeout() {
554+
return readTimeout;
555+
}
556+
557+
/**
558+
* Set the read timeout (in milliseconds), i.e. the response timeout
559+
* while waiting for data after the connection is established.
560+
* A value of 0 means the HTTP client's default response timeout is used,
561+
* otherwise values must be between 1 and {@link Integer#MAX_VALUE}.
562+
* The timeout is applied to each request via its request configuration;
563+
* other request configuration defaults of the underlying HTTP client
564+
* are preserved.
565+
* @param readTimeout Read timeout in milliseconds
566+
* @return API client
567+
* @throws IllegalArgumentException if readTimeout is negative
568+
*/
569+
public ApiClient setReadTimeout(int readTimeout) {
570+
if (readTimeout < 0) {
571+
throw new IllegalArgumentException("readTimeout must not be negative");
572+
}
573+
this.readTimeout = readTimeout;
574+
return this;
575+
}
537576

538577
/**
539578
* Get the date format used to parse/format date parameters.
@@ -1115,6 +1154,20 @@ public <T> T invokeAPI(
11151154
HttpClientContext context = HttpClientContext.create();
11161155
context.setCookieStore(store);
11171156

1157+
if (connectionTimeout > 0 || readTimeout > 0) {
1158+
// start from the default request configuration of the underlying HTTP client, if
1159+
// accessible, so that only the configured timeouts are overridden
1160+
RequestConfig defaultConfig = httpClient instanceof Configurable ? ((Configurable) httpClient).getConfig() : null;
1161+
RequestConfig.Builder requestConfigBuilder = defaultConfig == null ? RequestConfig.custom() : RequestConfig.copy(defaultConfig);
1162+
if (connectionTimeout > 0) {
1163+
requestConfigBuilder.setConnectTimeout(Timeout.ofMilliseconds(connectionTimeout));
1164+
}
1165+
if (readTimeout > 0) {
1166+
requestConfigBuilder.setResponseTimeout(Timeout.ofMilliseconds(readTimeout));
1167+
}
1168+
context.setRequestConfig(requestConfigBuilder.build());
1169+
}
1170+
11181171
ContentType contentTypeObj = getContentType(contentType);
11191172
if (body != null || !formParams.isEmpty()) {
11201173
if (isBodyAllowed(method)) {

0 commit comments

Comments
 (0)