|
14 | 14 |
|
15 | 15 | import com.google.gson.annotations.SerializedName; |
16 | 16 | import com.google.gson.reflect.TypeToken; |
17 | | -import com.squareup.okhttp.*; |
| 17 | +import com.squareup.okhttp.Call; |
| 18 | +import com.squareup.okhttp.Callback; |
| 19 | +import com.squareup.okhttp.ConnectionPool; |
| 20 | +import com.squareup.okhttp.FormEncodingBuilder; |
| 21 | +import com.squareup.okhttp.Headers; |
| 22 | +import com.squareup.okhttp.MediaType; |
| 23 | +import com.squareup.okhttp.MultipartBuilder; |
| 24 | +import com.squareup.okhttp.OkHttpClient; |
| 25 | +import com.squareup.okhttp.Request; |
| 26 | +import com.squareup.okhttp.RequestBody; |
| 27 | +import com.squareup.okhttp.Response; |
18 | 28 | import com.squareup.okhttp.internal.http.HttpMethod; |
19 | 29 | import com.squareup.okhttp.logging.HttpLoggingInterceptor; |
20 | 30 | import com.squareup.okhttp.logging.HttpLoggingInterceptor.Level; |
21 | 31 | import com.volcengine.auth.Authentication; |
22 | 32 | import com.volcengine.auth.CredentialProvider; |
23 | 33 | import com.volcengine.endpoint.DefaultEndpointProvider; |
24 | 34 | import com.volcengine.endpoint.EndpointResolver; |
25 | | -import com.volcengine.interceptor.*; |
| 35 | +import com.volcengine.interceptor.BuildRequestInterceptor; |
| 36 | +import com.volcengine.interceptor.DeserializedResponseInterceptor; |
| 37 | +import com.volcengine.interceptor.InitInterceptorContext; |
| 38 | +import com.volcengine.interceptor.InterceptorChain; |
| 39 | +import com.volcengine.interceptor.InterceptorContext; |
| 40 | +import com.volcengine.interceptor.ResolveEndpointInterceptor; |
| 41 | +import com.volcengine.interceptor.ResponseInterceptorContext; |
| 42 | +import com.volcengine.interceptor.SignRequestInterceptor; |
26 | 43 | import com.volcengine.model.AbstractResponse; |
27 | 44 | import com.volcengine.model.ResponseMetadata; |
| 45 | +import com.volcengine.retryer.BackoffStrategy; |
| 46 | +import com.volcengine.retryer.DefaultRetryerSetting; |
| 47 | +import com.volcengine.retryer.RetryCondition; |
| 48 | +import com.volcengine.retryer.Retryer; |
28 | 49 | import com.volcengine.sign.Credentials; |
29 | 50 | import com.volcengine.sign.ServiceInfo; |
30 | 51 | import com.volcengine.sign.VolcstackSign; |
|
37 | 58 | import org.threeten.bp.OffsetDateTime; |
38 | 59 | import org.threeten.bp.format.DateTimeFormatter; |
39 | 60 |
|
40 | | -import javax.net.ssl.*; |
| 61 | +import javax.net.ssl.HostnameVerifier; |
| 62 | +import javax.net.ssl.KeyManager; |
| 63 | +import javax.net.ssl.SSLContext; |
| 64 | +import javax.net.ssl.SSLSession; |
| 65 | +import javax.net.ssl.TrustManager; |
| 66 | +import javax.net.ssl.TrustManagerFactory; |
| 67 | +import javax.net.ssl.X509TrustManager; |
41 | 68 | import java.io.File; |
42 | 69 | import java.io.IOException; |
43 | 70 | import java.io.InputStream; |
|
57 | 84 | import java.security.cert.CertificateFactory; |
58 | 85 | import java.security.cert.X509Certificate; |
59 | 86 | import java.text.DateFormat; |
60 | | -import java.util.*; |
| 87 | +import java.util.ArrayList; |
| 88 | +import java.util.Collection; |
| 89 | +import java.util.Collections; |
| 90 | +import java.util.Date; |
| 91 | +import java.util.HashMap; |
| 92 | +import java.util.List; |
| 93 | +import java.util.Map; |
61 | 94 | import java.util.Map.Entry; |
| 95 | +import java.util.Set; |
62 | 96 | import java.util.concurrent.TimeUnit; |
| 97 | +import java.util.concurrent.atomic.AtomicInteger; |
63 | 98 | import java.util.regex.Matcher; |
64 | 99 | import java.util.regex.Pattern; |
65 | 100 |
|
@@ -105,6 +140,12 @@ public class ApiClient { |
105 | 140 |
|
106 | 141 | private Boolean useDualStack; |
107 | 142 |
|
| 143 | + private boolean autoRetry = DefaultRetryerSetting.DEFAULT_AUTO_RETRY_ENABLED; |
| 144 | + |
| 145 | + private final Retryer retryer = DefaultRetryerSetting.DEFAULT_RETRYER; |
| 146 | + |
| 147 | + private static final SdkLogger LOGGER = SdkLogger.getLogger(ApiClient.class); |
| 148 | + |
108 | 149 | /* |
109 | 150 | * Constructor for ApiClient |
110 | 151 | */ |
@@ -133,6 +174,7 @@ public ApiClient() { |
133 | 174 | interceptorChain.appendRequestInterceptor(new SignRequestInterceptor()); |
134 | 175 |
|
135 | 176 | interceptorChain.appendResponseInterceptor(new DeserializedResponseInterceptor()); |
| 177 | + SdkLogger.enableConsoleOutput(java.util.logging.Level.WARNING); |
136 | 178 | } |
137 | 179 |
|
138 | 180 | /** |
@@ -434,9 +476,11 @@ public ApiClient setDebugging(boolean debugging) { |
434 | 476 | loggingInterceptor = new HttpLoggingInterceptor(); |
435 | 477 | loggingInterceptor.setLevel(Level.BODY); |
436 | 478 | httpClient.interceptors().add(loggingInterceptor); |
| 479 | + SdkLogger.enableConsoleOutput(java.util.logging.Level.ALL); |
437 | 480 | } else { |
438 | 481 | httpClient.interceptors().remove(loggingInterceptor); |
439 | 482 | loggingInterceptor = null; |
| 483 | + SdkLogger.enableConsoleOutput(java.util.logging.Level.WARNING); |
440 | 484 | } |
441 | 485 | } |
442 | 486 | this.debugging = debugging; |
@@ -964,19 +1008,71 @@ public <T> ApiResponse<T> execute(Call call, final Type returnType, boolean... i |
964 | 1008 | responseInterceptorContext.setCommon(isCommon.length > 0 && isCommon[0]); |
965 | 1009 | responseInterceptorContext.setReturnType(returnType); |
966 | 1010 | context.setResponseContext(responseInterceptorContext); |
967 | | - |
968 | 1011 | context.setApiClient(this); |
969 | 1012 |
|
970 | | - try { |
971 | | - this.interceptorChain.executeRequest(context); |
972 | | - Call finalCall = context.requestContext.getCall(); |
973 | | - Response response = finalCall.execute(); |
974 | | - context.getResponseContext().setOriginalResponse(response); |
975 | | - this.interceptorChain.executeResponse(context); |
976 | | - return new ApiResponse<T>(response.code(), response.headers().toMultimap(), (T) context.getResponseContext().getData()); |
977 | | - } catch (IOException e) { |
978 | | - throw new ApiException(e); |
| 1013 | + int numMaxRetries = retryer.getNumMaxRetries(); |
| 1014 | + ApiException apiException; |
| 1015 | + ApiResponse<T> apiResponse = null; |
| 1016 | + for (int retryCount = 0; retryCount <= numMaxRetries; retryCount++) { |
| 1017 | + apiException = null; |
| 1018 | + apiResponse = null; |
| 1019 | + try { |
| 1020 | + this.interceptorChain.executeRequest(context); |
| 1021 | + } catch (ApiException e) { |
| 1022 | + throw e; |
| 1023 | + } |
| 1024 | + |
| 1025 | + try { |
| 1026 | + Call finalCall = context.getApiClient().getHttpClient().newCall(context.getRequestContext().getRequest()); |
| 1027 | + Response response = finalCall.execute(); |
| 1028 | + context.getResponseContext().setOriginalResponse(response); |
| 1029 | + this.interceptorChain.executeResponse(context); |
| 1030 | + apiResponse = new ApiResponse<T>(response.code(), response.headers().toMultimap(), (T) context.getResponseContext().getData()); |
| 1031 | + } catch (IOException e) { |
| 1032 | + apiException = new ApiException(e); |
| 1033 | + }catch (ApiException e) { |
| 1034 | + apiException = handleApiResponseException(e); |
| 1035 | + } |
| 1036 | + |
| 1037 | + if (!requestShouldRetry(apiResponse, retryCount, apiException)) { |
| 1038 | + if (apiException != null) { |
| 1039 | + throw apiException; |
| 1040 | + } |
| 1041 | + return apiResponse; |
| 1042 | + } |
| 1043 | + } |
| 1044 | + |
| 1045 | + return apiResponse; |
| 1046 | + } |
| 1047 | + |
| 1048 | + private ApiException handleApiResponseException(ApiException apiException) { |
| 1049 | + if (apiException.getResponseBody() != null) { |
| 1050 | + StringBuilder builder = new StringBuilder(); |
| 1051 | + Map<String, ResponseMetadata> meta = new HashMap<>(); |
| 1052 | + try { |
| 1053 | + if (!convertResponseBody(apiException.getResponseBody(), builder, meta)) { |
| 1054 | + apiException.setResponseMetadata(meta.get("ResponseMetadata")); |
| 1055 | + } |
| 1056 | + }catch (Exception e) { |
| 1057 | + return apiException; |
| 1058 | + } |
979 | 1059 | } |
| 1060 | + return apiException; |
| 1061 | + } |
| 1062 | + |
| 1063 | + private boolean requestShouldRetry(ApiResponse apiResponse, int retryCount, ApiException lastException) throws ApiException { |
| 1064 | + if (autoRetry && retryer.shouldRetry(apiResponse, retryCount, lastException)) { |
| 1065 | + try { |
| 1066 | + long delay = retryer.getBackoffDelay(retryCount); |
| 1067 | + LOGGER.debug("retryCount: " + (retryCount + 1) + ", delay: " + delay + ", backoffStrategy: " + retryer.getBackoffStrategy().getClass().getName()); |
| 1068 | + Thread.sleep(delay); |
| 1069 | + } catch (Exception e) { |
| 1070 | + throw new ApiException(e); |
| 1071 | + } |
| 1072 | + return true; |
| 1073 | + } |
| 1074 | + |
| 1075 | + return false; |
980 | 1076 | } |
981 | 1077 |
|
982 | 1078 | /** |
@@ -1011,34 +1107,76 @@ public <T> void executeAsync(Call call, final Type returnType, final ApiCallback |
1011 | 1107 | context.setResponseContext(responseInterceptorContext); |
1012 | 1108 |
|
1013 | 1109 | context.setApiClient(this); |
| 1110 | + |
| 1111 | + final int maxRetries = retryer.getNumMaxRetries(); |
| 1112 | + final AtomicInteger retryCount = new AtomicInteger(0); |
| 1113 | + attemptAsync(context, callback, retryCount, maxRetries); |
| 1114 | + } |
| 1115 | + |
| 1116 | + private <T> void attemptAsync( |
| 1117 | + final InterceptorContext context, |
| 1118 | + final ApiCallback<T> callback, |
| 1119 | + final AtomicInteger retryCount, |
| 1120 | + final int maxRetries |
| 1121 | + ) { |
1014 | 1122 | try { |
1015 | 1123 | this.interceptorChain.executeRequest(context); |
1016 | 1124 | } catch (ApiException e) { |
1017 | 1125 | callback.onFailure(e, 0, null); |
1018 | 1126 | return; |
1019 | 1127 | } |
1020 | | - Callback okHttpCallBack = new Callback() { |
| 1128 | + |
| 1129 | + Call okhttpCall = getHttpClient().newCall(context.getRequestContext().getRequest()); |
| 1130 | + okhttpCall.enqueue(new Callback() { |
1021 | 1131 | @Override |
1022 | | - public void onFailure(Request request, IOException e) { |
1023 | | - callback.onFailure(new ApiException(e), 0, null); |
| 1132 | + public void onFailure(Request req, IOException ioe) { |
| 1133 | + int current = retryCount.get(); |
| 1134 | + ApiException apiException = new ApiException(ioe); |
| 1135 | + try { |
| 1136 | + if (autoRetry && current < maxRetries |
| 1137 | + && requestShouldRetry(null, current, apiException)) { |
| 1138 | + retryCount.incrementAndGet(); |
| 1139 | + attemptAsync(context, callback, retryCount, maxRetries); |
| 1140 | + } else { |
| 1141 | + callback.onFailure(apiException, 0, null); |
| 1142 | + } |
| 1143 | + }catch (ApiException e) { |
| 1144 | + callback.onFailure(apiException, 0, null); |
| 1145 | + } |
| 1146 | + |
1024 | 1147 | } |
1025 | 1148 |
|
1026 | 1149 | @Override |
1027 | 1150 | public void onResponse(Response response) throws IOException { |
1028 | | - T result; |
| 1151 | + T result = null; |
| 1152 | + ApiException apiException = null; |
| 1153 | + ApiResponse<T> apiResponse = null; |
| 1154 | + int current = retryCount.getAndIncrement(); |
| 1155 | + |
1029 | 1156 | try { |
1030 | 1157 | context.getResponseContext().setOriginalResponse(response); |
1031 | 1158 | context.getApiClient().interceptorChain.executeResponse(context); |
1032 | 1159 | result = (T) context.responseContext.getData(); |
| 1160 | + apiResponse = new ApiResponse<T>(response.code(), response.headers().toMultimap(), result); |
1033 | 1161 | } catch (ApiException e) { |
1034 | | - callback.onFailure(e, response.code(), response.headers().toMultimap()); |
1035 | | - return; |
| 1162 | + apiException = handleApiResponseException(e); |
1036 | 1163 | } |
1037 | | - callback.onSuccess(result, response.code(), response.headers().toMultimap()); |
1038 | | - } |
1039 | | - }; |
1040 | 1164 |
|
1041 | | - ((InterceptorContext) call).getRequestContext().getCall().enqueue(okHttpCallBack); |
| 1165 | + try { |
| 1166 | + if (!requestShouldRetry(apiResponse, current, apiException)) { |
| 1167 | + if (apiException != null) { |
| 1168 | + callback.onFailure(apiException, response.code(), response.headers().toMultimap()); |
| 1169 | + }else { |
| 1170 | + callback.onSuccess(result, response.code(), response.headers().toMultimap()); |
| 1171 | + } |
| 1172 | + }else { |
| 1173 | + attemptAsync(context, callback,retryCount , maxRetries); |
| 1174 | + } |
| 1175 | + }catch (ApiException e){ |
| 1176 | + callback.onFailure(apiException, response.code(), response.headers().toMultimap()); |
| 1177 | + } |
| 1178 | + } |
| 1179 | + }); |
1042 | 1180 | } |
1043 | 1181 |
|
1044 | 1182 | /** |
@@ -1710,4 +1848,78 @@ public ApiClient setKeepAliveDurationMs(Integer keepAliveDurationMs) { |
1710 | 1848 | this.httpClient.setConnectionPool(new ConnectionPool(maxIdleConns, keepAliveDurationMs)); |
1711 | 1849 | return this; |
1712 | 1850 | } |
| 1851 | + |
| 1852 | + public boolean isAutoRetry() { |
| 1853 | + return autoRetry; |
| 1854 | + } |
| 1855 | + |
| 1856 | + public ApiClient setAutoRetry(boolean autoRetry) { |
| 1857 | + this.autoRetry = autoRetry; |
| 1858 | + return this; |
| 1859 | + } |
| 1860 | + |
| 1861 | + public int getNumMaxRetries() { |
| 1862 | + return retryer.getNumMaxRetries(); |
| 1863 | + } |
| 1864 | + |
| 1865 | + public ApiClient setNumMaxRetries(int numMaxRetries) { |
| 1866 | + retryer.setNumMaxRetries(numMaxRetries); |
| 1867 | + return this; |
| 1868 | + } |
| 1869 | + |
| 1870 | + public Set<String> getRetryErrorCodes() { |
| 1871 | + return retryer.getRetryCondition().getRetryErrorCodes(); |
| 1872 | + } |
| 1873 | + |
| 1874 | + public ApiClient addRetryErrorCode(String retryErrorCode) { |
| 1875 | + retryer.getRetryCondition().addRetryErrorCode(retryErrorCode); |
| 1876 | + return this; |
| 1877 | + } |
| 1878 | + |
| 1879 | + public ApiClient addRetryErrorCodes(Set<String> retryErrorCodes) { |
| 1880 | + retryer.getRetryCondition().addRetryErrorCodes(retryErrorCodes); |
| 1881 | + return this; |
| 1882 | + } |
| 1883 | + |
| 1884 | + public long getMinRetryDelayMs() { |
| 1885 | + return retryer.getBackoffStrategy().getMinRetryDelayMs(); |
| 1886 | + } |
| 1887 | + |
| 1888 | + public ApiClient setMinRetryDelayMs(long minRetryDelayMs) { |
| 1889 | + retryer.getBackoffStrategy().setMinRetryDelayMs(minRetryDelayMs); |
| 1890 | + return this; |
| 1891 | + } |
| 1892 | + |
| 1893 | + public long getMaxRetryDelayMs() { |
| 1894 | + return retryer.getBackoffStrategy().getMaxRetryDelayMs(); |
| 1895 | + } |
| 1896 | + |
| 1897 | + public ApiClient setMaxRetryDelayMs(long maxRetryDelayMs) { |
| 1898 | + retryer.getBackoffStrategy().setMaxRetryDelayMs(maxRetryDelayMs); |
| 1899 | + return this; |
| 1900 | + } |
| 1901 | + |
| 1902 | + public RetryCondition getRetryCondition() { |
| 1903 | + return retryer.getRetryCondition(); |
| 1904 | + } |
| 1905 | + |
| 1906 | + public ApiClient setRetryCondition(RetryCondition retryCondition) throws ApiException { |
| 1907 | + Set<String> retryErrorCodes = retryer.getRetryCondition().getRetryErrorCodes(); |
| 1908 | + retryCondition.addRetryErrorCodes(retryErrorCodes); |
| 1909 | + retryer.setRetryCondition(retryCondition); |
| 1910 | + return this; |
| 1911 | + } |
| 1912 | + |
| 1913 | + public BackoffStrategy getBackoffStrategy() { |
| 1914 | + return retryer.getBackoffStrategy(); |
| 1915 | + } |
| 1916 | + |
| 1917 | + public ApiClient setBackoffStrategy(BackoffStrategy backoffStrategy) throws ApiException { |
| 1918 | + long minRetryDelayMs = retryer.getBackoffStrategy().getMinRetryDelayMs(); |
| 1919 | + long maxRetryDelayMs = retryer.getBackoffStrategy().getMaxRetryDelayMs(); |
| 1920 | + backoffStrategy.setMinRetryDelayMs(minRetryDelayMs); |
| 1921 | + backoffStrategy.setMaxRetryDelayMs(maxRetryDelayMs); |
| 1922 | + retryer.setBackoffStrategy(backoffStrategy); |
| 1923 | + return this; |
| 1924 | + } |
1713 | 1925 | } |
0 commit comments