Skip to content

Commit 2f0f5d5

Browse files
committed
feat: Support retry in open api client
1 parent 5821922 commit 2f0f5d5

File tree

5 files changed

+138
-2
lines changed

5 files changed

+138
-2
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Apollo Java 2.5.0
88

99
* [Feature Provide a new open APl to return the organization list](https://github.com/apolloconfig/apollo-java/pull/102)
1010
* [Feature Added a new feature to get instance count by namespace.](https://github.com/apolloconfig/apollo-java/pull/103)
11+
* [Feature Support retry in open api client.](https://github.com/apolloconfig/apollo-java/pull/105)
1112

1213
------------------
1314
All issues and pull requests are [here](https://github.com/apolloconfig/apollo-java/milestone/5?closed=1)

apollo-openapi/src/main/java/com/ctrip/framework/apollo/openapi/client/ApolloOpenApiClient.java

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package com.ctrip.framework.apollo.openapi.client;
1818

1919
import com.ctrip.framework.apollo.openapi.client.constant.ApolloOpenApiConstants;
20+
import com.ctrip.framework.apollo.openapi.client.extend.ApolloStandardHttpRequestRetryHandler;
21+
import com.ctrip.framework.apollo.openapi.client.extend.IdempotentHttpMethod;
2022
import com.ctrip.framework.apollo.openapi.client.service.AppOpenApiService;
2123
import com.ctrip.framework.apollo.openapi.client.service.ClusterOpenApiService;
2224
import com.ctrip.framework.apollo.openapi.client.service.ItemOpenApiService;
@@ -55,10 +57,13 @@ public class ApolloOpenApiClient {
5557
private final InstanceOpenApiService instanceService;
5658
private static final Gson GSON = new GsonBuilder().setDateFormat(ApolloOpenApiConstants.JSON_DATE_FORMAT).create();
5759

58-
private ApolloOpenApiClient(String portalUrl, String token, RequestConfig requestConfig) {
60+
private ApolloOpenApiClient(String portalUrl, String token, RequestConfig requestConfig,
61+
int retryCount, IdempotentHttpMethod[] idempotentHttpMethods) {
5962
this.portalUrl = portalUrl;
6063
this.token = token;
6164
CloseableHttpClient client = HttpClients.custom().setDefaultRequestConfig(requestConfig)
65+
.setRetryHandler(retryCount > 0 ?
66+
new ApolloStandardHttpRequestRetryHandler(retryCount, idempotentHttpMethods) : null)
6267
.setDefaultHeaders(Lists.newArrayList(new BasicHeader("Authorization", token))).build();
6368

6469
String baseUrl = this.portalUrl + ApolloOpenApiConstants.OPEN_API_V1_PREFIX;
@@ -273,6 +278,8 @@ public static class ApolloOpenApiClientBuilder {
273278
private String token;
274279
private int connectTimeout = -1;
275280
private int readTimeout = -1;
281+
private int retryCount = -1;
282+
private IdempotentHttpMethod[] idempotentHttpMethods;
276283

277284
/**
278285
* @param portalUrl The apollo portal url, e.g http://localhost:8070
@@ -306,6 +313,22 @@ public ApolloOpenApiClientBuilder withReadTimeout(int readTimeout) {
306313
return this;
307314
}
308315

316+
/**
317+
* @param retryCount execute retry when an exception occurs, default no retry
318+
*/
319+
public ApolloOpenApiClientBuilder withRetryCount(int retryCount) {
320+
this.retryCount = retryCount;
321+
return this;
322+
}
323+
324+
/**
325+
* @param idempotentHttpMethods idempotent HTTP methods will directly execute retries when exception
326+
*/
327+
public ApolloOpenApiClientBuilder withIdempotentHttpMethods(IdempotentHttpMethod... idempotentHttpMethods) {
328+
this.idempotentHttpMethods = idempotentHttpMethods;
329+
return this;
330+
}
331+
309332
public ApolloOpenApiClient build() {
310333
Preconditions.checkArgument(!Strings.isNullOrEmpty(portalUrl), "Portal url should not be null or empty!");
311334
Preconditions.checkArgument(portalUrl.startsWith("http://") || portalUrl.startsWith("https://"), "Portal url should start with http:// or https://" );
@@ -322,7 +345,7 @@ public ApolloOpenApiClient build() {
322345
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(connectTimeout)
323346
.setSocketTimeout(readTimeout).build();
324347

325-
return new ApolloOpenApiClient(portalUrl, token, requestConfig);
348+
return new ApolloOpenApiClient(portalUrl, token, requestConfig, retryCount, idempotentHttpMethods);
326349
}
327350
}
328351
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2022 Apollo Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
package com.ctrip.framework.apollo.openapi.client.extend;
18+
19+
import com.google.common.collect.Collections2;
20+
import com.google.common.collect.Lists;
21+
import java.util.Collections;
22+
import java.util.HashSet;
23+
import java.util.List;
24+
import java.util.Objects;
25+
import java.util.Optional;
26+
import java.util.Set;
27+
import org.apache.http.HttpRequest;
28+
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
29+
30+
import javax.net.ssl.SSLException;
31+
import java.net.ConnectException;
32+
import java.net.NoRouteToHostException;
33+
import java.net.UnknownHostException;
34+
import java.util.Arrays;
35+
import java.util.Locale;
36+
37+
/**
38+
* @author zth9
39+
* @date 2025-05-07
40+
*/
41+
public class ApolloStandardHttpRequestRetryHandler extends DefaultHttpRequestRetryHandler {
42+
43+
private final Set<String> idempotentMethods;
44+
45+
public ApolloStandardHttpRequestRetryHandler(int retryCount, IdempotentHttpMethod[] httpMethods) {
46+
super(retryCount, false, Arrays.asList(
47+
UnknownHostException.class,
48+
ConnectException.class,
49+
NoRouteToHostException.class,
50+
SSLException.class));
51+
this.idempotentMethods = new HashSet<>();
52+
if (httpMethods == null || httpMethods.length == 0) {
53+
// default set safe idempotent http method
54+
httpMethods = IdempotentHttpMethod.safe();
55+
}
56+
for (IdempotentHttpMethod httpMethod : httpMethods) {
57+
if (httpMethod == null) {
58+
continue;
59+
}
60+
idempotentMethods.add(httpMethod.name());
61+
}
62+
}
63+
64+
@Override
65+
protected boolean handleAsIdempotent(final HttpRequest request) {
66+
String method = request.getRequestLine().getMethod().toUpperCase(Locale.ROOT);
67+
return idempotentMethods.contains(method);
68+
}
69+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2022 Apollo Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
package com.ctrip.framework.apollo.openapi.client.extend;
18+
19+
/**
20+
* @author zth9
21+
* @date 2025-05-11
22+
*/
23+
public enum IdempotentHttpMethod {
24+
GET, HEAD, PUT, DELETE, OPTIONS, TRACE;
25+
26+
/**
27+
* Usually, these methods are idempotent
28+
*/
29+
public static IdempotentHttpMethod[] safe() {
30+
return new IdempotentHttpMethod[]{GET, HEAD, OPTIONS, TRACE};
31+
}
32+
33+
/**
34+
* Standard HTTP idempotent method. While PUT and DELETE are technically idempotent, repeated
35+
* requests can yield different responses—such as a 404 on a second delete
36+
*/
37+
public static IdempotentHttpMethod[] standard() {
38+
return new IdempotentHttpMethod[]{GET, HEAD, PUT, DELETE, OPTIONS, TRACE};
39+
}
40+
}

apollo-openapi/src/test/java/com/ctrip/framework/apollo/openapi/client/ApolloOpenApiClientIntegrationTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static org.junit.jupiter.api.Assertions.assertEquals;
2020
import static org.junit.jupiter.api.Assertions.assertThrows;
2121

22+
import com.ctrip.framework.apollo.openapi.client.extend.IdempotentHttpMethod;
2223
import com.ctrip.framework.apollo.openapi.dto.NamespaceReleaseDTO;
2324
import com.ctrip.framework.apollo.openapi.dto.OpenAppDTO;
2425
import com.ctrip.framework.apollo.openapi.dto.OpenAppNamespaceDTO;
@@ -63,6 +64,8 @@ ApolloOpenApiClient newClient() {
6364
.withToken(someToken)
6465
.withReadTimeout(2000 * 1000)
6566
.withConnectTimeout(2000 * 1000)
67+
.withRetryCount(3)
68+
.withIdempotentHttpMethods(IdempotentHttpMethod.safe())
6669
.build();
6770
}
6871

0 commit comments

Comments
 (0)