|
2 | 2 |
|
3 | 3 | import java.io.IOException; |
4 | 4 |
|
| 5 | +import com.microsoft.graph.httpcore.middlewareoption.IShouldRetry; |
| 6 | +import com.microsoft.graph.httpcore.middlewareoption.MiddlewareType; |
| 7 | +import com.microsoft.graph.httpcore.middlewareoption.RedirectOptions; |
5 | 8 | import com.microsoft.graph.httpcore.middlewareoption.RetryOptions; |
6 | 9 |
|
7 | 10 | import okhttp3.Interceptor; |
8 | 11 | import okhttp3.Request; |
9 | 12 | import okhttp3.Response; |
10 | 13 |
|
11 | 14 | public class RetryHandler implements Interceptor{ |
| 15 | + |
| 16 | + public final MiddlewareType MIDDLEWARE_TYPE = MiddlewareType.RETRY; |
12 | 17 |
|
13 | | - /** |
14 | | - * Maximum number of allowed retries if the server responds with a HTTP code |
15 | | - * in our retry code list. Default value is 1. |
16 | | - */ |
17 | | - private final int maxRetries = 2; |
18 | | - |
19 | | - /** |
20 | | - * Retry interval between subsequent requests, in milliseconds. Default |
21 | | - * value is 1 second. |
| 18 | + private RetryOptions mRetryOption; |
| 19 | + |
| 20 | + /* |
| 21 | + * constant string being used |
22 | 22 | */ |
23 | | - private long retryInterval = 1000; |
24 | | - private final int DELAY_MILLISECONDS = 1000; |
| 23 | + private final String RETRY_ATTEMPT_HEADER = "Retry-Attempt"; |
25 | 24 | private final String RETRY_AFTER = "Retry-After"; |
26 | 25 | private final String TRANSFER_ENCODING = "Transfer-Encoding"; |
| 26 | + private final String TRANSFER_ENCODING_CHUNKED = "chunked"; |
| 27 | + private final String APPLICATION_OCTET_STREAM = "application/octet-stream"; |
| 28 | + private final String CONTENT_TYPE = "Content-Type"; |
| 29 | + |
| 30 | + public static final int MSClientErrorCodeTooManyRequests = 429; |
| 31 | + public static final int MSClientErrorCodeServiceUnavailable = 503; |
| 32 | + public static final int MSClientErrorCodeGatewayTimeout = 504; |
27 | 33 |
|
28 | | - private final int MSClientErrorCodeTooManyRequests = 429; |
29 | | - private final int MSClientErrorCodeServiceUnavailable = 503; |
30 | | - private final int MSClientErrorCodeGatewayTimeout = 504; |
31 | | - private final RetryOptions mRetryOption; |
| 34 | + private final long DELAY_MILLISECONDS = 1000; |
32 | 35 |
|
33 | | - public RetryHandler(RetryOptions option) { |
34 | | - super(); |
35 | | - this.mRetryOption = option; |
| 36 | + /* |
| 37 | + * @retryOption Create Retry handler using retry option |
| 38 | + */ |
| 39 | + public RetryHandler(RetryOptions retryOption) { |
| 40 | + this.mRetryOption = retryOption; |
| 41 | + if(this.mRetryOption == null) { |
| 42 | + this.mRetryOption = new RetryOptions(); |
| 43 | + } |
36 | 44 | } |
37 | | - |
| 45 | + /* |
| 46 | + * Initialize retry handler with default retry option |
| 47 | + */ |
38 | 48 | public RetryHandler() { |
39 | 49 | this(null); |
40 | 50 | } |
41 | 51 |
|
42 | | - public boolean retryRequest(Response response, int executionCount, Request request) { |
| 52 | + private boolean retryRequest(Response response, int executionCount, Request request, RetryOptions retryOptions) { |
43 | 53 |
|
44 | | - RetryOptions retryOption = request.tag(RetryOptions.class); |
45 | | - if(retryOption != null) { |
46 | | - return retryOption.shouldRetry().shouldRetry(response, executionCount, request); |
| 54 | + // Should retry option |
| 55 | + // Use should retry common for all requests |
| 56 | + IShouldRetry shouldRetryCallback = null; |
| 57 | + if(retryOptions != null) { |
| 58 | + shouldRetryCallback = retryOptions.shouldRetry(); |
47 | 59 | } |
48 | | - if(mRetryOption != null) { |
49 | | - return mRetryOption.shouldRetry().shouldRetry(response, executionCount, request); |
| 60 | + // Call should retry callback |
| 61 | + if(shouldRetryCallback != null) { |
| 62 | + shouldRetryCallback.shouldRetry(response, executionCount, request, retryOptions.delay()); |
50 | 63 | } |
51 | 64 |
|
52 | 65 | boolean shouldRetry = false; |
| 66 | + // Status codes 429 503 504 |
53 | 67 | int statusCode = response.code(); |
54 | | - shouldRetry = (executionCount < maxRetries) && checkStatus(statusCode) && isBuffered(response, request); |
| 68 | + // Only requests with payloads that are buffered/rewindable are supported. |
| 69 | + // Payloads with forward only streams will be have the responses returned |
| 70 | + // without any retry attempt. |
| 71 | + shouldRetry = (executionCount <= retryOptions.maxRetries()) && checkStatus(statusCode) && isBuffered(response, request); |
55 | 72 |
|
56 | 73 | if(shouldRetry) { |
57 | | - String retryAfterHeader = response.header(RETRY_AFTER); |
58 | | - if(retryAfterHeader != null) |
59 | | - retryInterval = Long.parseLong(retryAfterHeader); |
60 | | - else |
61 | | - retryInterval = (long)Math.pow(2.0, (double)executionCount) * DELAY_MILLISECONDS; |
| 74 | + long retryInterval = getRetryAfter(response, retryOptions.delay(), executionCount); |
| 75 | + try { |
| 76 | + Thread.sleep(retryInterval); |
| 77 | + } catch (InterruptedException e) { |
| 78 | + e.printStackTrace(); |
| 79 | + } |
62 | 80 | } |
63 | 81 | return shouldRetry; |
64 | 82 | } |
65 | | - |
66 | | - public long getRetryInterval() { |
67 | | - return retryInterval; |
| 83 | + |
| 84 | + private long getRetryAfter(Response response, long delay, int executionCount) { |
| 85 | + String retryAfterHeader = response.header(RETRY_AFTER); |
| 86 | + long retryDelay = RetryOptions.DEFAULT_DELAY; |
| 87 | + if(retryAfterHeader != null) { |
| 88 | + retryDelay = Long.parseLong(retryAfterHeader); |
| 89 | + } else { |
| 90 | + retryDelay = (long)Math.pow(2.0, (double)executionCount) * DELAY_MILLISECONDS; |
| 91 | + retryDelay = executionCount < 2 ? retryDelay : retryDelay + delay + (long)Math.random(); |
| 92 | + } |
| 93 | + return Math.min(retryDelay, RetryOptions.MAX_DELAY); |
68 | 94 | } |
69 | 95 |
|
70 | 96 | private boolean checkStatus(int statusCode) { |
71 | | - if (statusCode == MSClientErrorCodeTooManyRequests || statusCode == MSClientErrorCodeServiceUnavailable |
72 | | - || statusCode == MSClientErrorCodeGatewayTimeout) |
73 | | - return true; |
74 | | - return false; |
| 97 | + return (statusCode == MSClientErrorCodeTooManyRequests || statusCode == MSClientErrorCodeServiceUnavailable |
| 98 | + || statusCode == MSClientErrorCodeGatewayTimeout); |
75 | 99 | } |
76 | 100 |
|
77 | 101 | private boolean isBuffered(Response response, Request request) { |
78 | 102 | String methodName = request.method(); |
| 103 | + if(methodName.equalsIgnoreCase("GET") || methodName.equalsIgnoreCase("DELETE")) |
| 104 | + return true; |
79 | 105 |
|
80 | 106 | boolean isHTTPMethodPutPatchOrPost = methodName.equalsIgnoreCase("POST") || |
81 | 107 | methodName.equalsIgnoreCase("PUT") || |
82 | 108 | methodName.equalsIgnoreCase("PATCH"); |
83 | 109 |
|
84 | | - //Header transferEncoding = response.getFirstHeader(TRANSFER_ENCODING); |
85 | | - String transferEncoding = response.header(TRANSFER_ENCODING); |
86 | | - boolean isTransferEncodingChunked = (transferEncoding != null) && |
87 | | - transferEncoding.equalsIgnoreCase("chunked"); |
88 | | - |
89 | | - if(request.body() != null && isHTTPMethodPutPatchOrPost && isTransferEncodingChunked) |
90 | | - return false; |
91 | | - return true; |
| 110 | + if(isHTTPMethodPutPatchOrPost) { |
| 111 | + boolean isStream = response.header(CONTENT_TYPE)!=null && response.header(CONTENT_TYPE).equalsIgnoreCase(APPLICATION_OCTET_STREAM); |
| 112 | + if(!isStream) { |
| 113 | + String transferEncoding = response.header(TRANSFER_ENCODING); |
| 114 | + boolean isTransferEncodingChunked = (transferEncoding != null) && |
| 115 | + transferEncoding.equalsIgnoreCase(TRANSFER_ENCODING_CHUNKED); |
| 116 | + if(request.body() != null && isTransferEncodingChunked) |
| 117 | + return true; |
| 118 | + } |
| 119 | + } |
| 120 | + return false; |
92 | 121 | } |
93 | 122 |
|
94 | 123 | @Override |
95 | 124 | public Response intercept(Chain chain) throws IOException { |
96 | 125 | Request request = chain.request(); |
97 | 126 |
|
98 | 127 | Response response = chain.proceed(request); |
99 | | - int executionCount = 0; |
100 | | - while(retryRequest(response, executionCount, request)) { |
| 128 | + |
| 129 | + // Use should retry pass along with this request |
| 130 | + RetryOptions retryOption = request.tag(RetryOptions.class); |
| 131 | + retryOption = retryOption != null ? retryOption : this.mRetryOption; |
| 132 | + |
| 133 | + int executionCount = 1; |
| 134 | + while(retryRequest(response, executionCount, request, retryOption)) { |
| 135 | + request = request.newBuilder().addHeader(RETRY_ATTEMPT_HEADER, String.valueOf(executionCount)).build(); |
101 | 136 | executionCount++; |
102 | 137 | response = chain.proceed(request); |
103 | 138 | } |
|
0 commit comments