public async Task <IBoxResponse <T> > ExecuteAsync <T>(IBoxRequest request) where T : class { // Need to account for special cases when the return type is a stream bool isStream = typeof(T) == typeof(Stream); var retryCounter = 0; ExponentialBackoff expBackoff = new ExponentialBackoff(); try { // TODO: yhu@ better handling of different request var isMultiPartRequest = request.GetType() == typeof(BoxMultiPartRequest); var isBinaryRequest = request.GetType() == typeof(BoxBinaryRequest); while (true) { HttpRequestMessage httpRequest = getHttpRequest(request, isMultiPartRequest, isBinaryRequest); Debug.WriteLine(string.Format("RequestUri: {0}", httpRequest.RequestUri)); HttpResponseMessage response = await getResponse(request, isStream, httpRequest).ConfigureAwait(false); //need to wait for Retry-After seconds and then retry request var retryAfterHeader = response.Headers.RetryAfter; // If we get a retryable/transient error code and this is not a multi part request (meaning a file upload, which cannot be retried // because the stream cannot be reset) and we haven't exceeded the number of allowed retries, then retry the request. // If we get a 202 code and has a retry-after header, we will retry after if (!isMultiPartRequest && (response.StatusCode == TooManyRequests || response.StatusCode == HttpStatusCode.InternalServerError || response.StatusCode == HttpStatusCode.BadGateway || response.StatusCode == HttpStatusCode.ServiceUnavailable || response.StatusCode == HttpStatusCode.GatewayTimeout || (response.StatusCode == HttpStatusCode.Accepted && retryAfterHeader != null)) && retryCounter++ < RetryLimit) { TimeSpan delay = expBackoff.GetRetryTimeout(retryCounter); Debug.WriteLine("HttpCode : {0}. Waiting for {1} seconds to retry request. RequestUri: {2}", response.StatusCode, delay.Seconds, httpRequest.RequestUri); await Task.Delay(delay); } else { BoxResponse <T> boxResponse = await getBoxResponse <T>(isStream, response).ConfigureAwait(false); return(boxResponse); } } } catch (Exception ex) { Debug.WriteLine(string.Format("Exception: {0}", ex.Message)); throw; } }
public void ExponentialBackoff_ValidResponse() { int retryCount = 1; double[] lowerBound = { 1, 2, 4, 8, 16, 32 }; double[] upperBound = { 3, 6, 12, 24, 48, 96 }; for (int i = 0; i < 6; i++) { ExponentialBackoff expBackoff = new ExponentialBackoff(); var backoffDelay = expBackoff.GetRetryTimeout(retryCount); Assert.IsTrue(lowerBound[i] <= backoffDelay.TotalSeconds, "Backoff Delay is not in the correct range."); Assert.IsTrue(backoffDelay.TotalSeconds <= upperBound[i], "Backoff Delay is not in the correct range."); retryCount++; } }
public async Task <IBoxResponse <T> > ExecuteAsync <T>(IBoxRequest request) where T : class { // Need to account for special cases when the return type is a stream bool isStream = typeof(T) == typeof(Stream); var retryCounter = 0; ExponentialBackoff expBackoff = new ExponentialBackoff(); try { // TODO: yhu@ better handling of different request var isMultiPartRequest = request.GetType() == typeof(BoxMultiPartRequest); var isBinaryRequest = request.GetType() == typeof(BoxBinaryRequest); while (true) { HttpRequestMessage httpRequest = null; if (isMultiPartRequest) { httpRequest = BuildMultiPartRequest(request as BoxMultiPartRequest); } else if (isBinaryRequest) { httpRequest = BuildBinaryRequest(request as BoxBinaryRequest); } else { httpRequest = BuildRequest(request); } // Add headers foreach (var kvp in request.HttpHeaders) { // They could not be added to the headers directly if (kvp.Key == Constants.RequestParameters.ContentMD5 || kvp.Key == Constants.RequestParameters.ContentRange) { httpRequest.Content.Headers.Add(kvp.Key, kvp.Value); } else { httpRequest.Headers.TryAddWithoutValidation(kvp.Key, kvp.Value); } } // If we are retrieving a stream, we should return without reading the entire response HttpCompletionOption completionOption = isStream ? HttpCompletionOption.ResponseHeadersRead : HttpCompletionOption.ResponseContentRead; Debug.WriteLine(string.Format("RequestUri: {0}", httpRequest.RequestUri)); HttpClient client = GetClient(request); // Not disposing the reponse since it will affect stream response var response = await client.SendAsync(httpRequest, completionOption).ConfigureAwait(false); //need to wait for Retry-After seconds and then retry request var retryAfterHeader = response.Headers.RetryAfter; // If we get a retryable/transient error code and this is not a multi part request (meaning a file upload, which cannot be retried // because the stream cannot be reset) and we haven't exceeded the number of allowed retries, then retry the request. // If we get a 202 code and has a retry-after header, we will retry after if (!isMultiPartRequest && (response.StatusCode == TooManyRequests || response.StatusCode == HttpStatusCode.InternalServerError || response.StatusCode == HttpStatusCode.GatewayTimeout || response.StatusCode == HttpStatusCode.BadGateway || (response.StatusCode == HttpStatusCode.Accepted && retryAfterHeader != null)) && retryCounter++ < RetryLimit) { TimeSpan delay = expBackoff.GetRetryTimeout(retryCounter); Debug.WriteLine("HttpCode : {0}. Waiting for {1} seconds to retry request. RequestUri: {2}", response.StatusCode, delay.Seconds, httpRequest.RequestUri); await Task.Delay(delay); } else { BoxResponse <T> boxResponse = new BoxResponse <T>(); boxResponse.Headers = response.Headers; // Translate the status codes that interest us boxResponse.StatusCode = response.StatusCode; switch (response.StatusCode) { case HttpStatusCode.OK: case HttpStatusCode.Created: case HttpStatusCode.NoContent: case HttpStatusCode.Found: case HttpStatusCode.PartialContent: // Download with range boxResponse.Status = ResponseStatus.Success; break; case HttpStatusCode.Accepted: boxResponse.Status = ResponseStatus.Pending; break; case HttpStatusCode.Unauthorized: boxResponse.Status = ResponseStatus.Unauthorized; break; case HttpStatusCode.Forbidden: boxResponse.Status = ResponseStatus.Forbidden; break; case TooManyRequests: boxResponse.Status = ResponseStatus.TooManyRequests; break; default: boxResponse.Status = ResponseStatus.Error; break; } if (isStream && boxResponse.Status == ResponseStatus.Success) { var resObj = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); boxResponse.ResponseObject = resObj as T; } else { boxResponse.ContentString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); // We can safely dispose the response now since all of it has been read response.Dispose(); } return(boxResponse); } } } catch (Exception ex) { Debug.WriteLine(string.Format("Exception: {0}", ex.Message)); throw; } }
private async Task <string> GetTokenAsync(string subType, string subId) { var retryCounter = 0; var expBackoff = new ExponentialBackoff(); var assertion = ConstructJWTAssertion(subId, subType); OAuthSession result; while (true) { try { result = await JWTAuthPostAsync(assertion).ConfigureAwait(false); return(result.AccessToken); } catch (BoxAPIException ex) { //need to wait for Retry-After seconds and then retry request var retryAfterHeader = ex.ResponseHeaders?.RetryAfter; // If we get a retryable/transient error code and this is not a multi part request (meaning a file upload, which cannot be retried // because the stream cannot be reset) and we haven't exceeded the number of allowed retries, then retry the request. // If we get a 202 code and has a retry-after header, we will retry after. // If we get a 400 due to exp claim issue, this can happen if the current system time is too different from the Box server time, so retry. var errorCode = ex.ErrorCode; var errorDescription = ex.ErrorDescription; if ((ex.StatusCode == HttpRequestHandler.TooManyRequests || ex.StatusCode == HttpStatusCode.InternalServerError || ex.StatusCode == HttpStatusCode.BadGateway || ex.StatusCode == HttpStatusCode.ServiceUnavailable || ex.StatusCode == HttpStatusCode.GatewayTimeout || (ex.StatusCode == HttpStatusCode.Accepted && retryAfterHeader != null) || (ex.StatusCode == HttpStatusCode.BadRequest && errorCode.Contains("invalid_grant") && errorDescription.Contains("exp"))) && retryCounter++ < HttpRequestHandler.RetryLimit) { TimeSpan delay = expBackoff.GetRetryTimeout(retryCounter); // If the response contains a Retry-After header, override the exponential back-off delay value if (retryAfterHeader != null && int.TryParse(retryAfterHeader.ToString(), out var timeToWait)) { delay = new TimeSpan(0, 0, 0, 0, timeToWait); } // Before we retry the JWT Authentication request, we must regenerate the JTI claim with an updated DateTimeOffset. // A delay is added to the JWT time, to account for the time of the upcoming wait. var serverDate = ex.ResponseHeaders?.Date; if (serverDate.HasValue) { var date = serverDate.Value; assertion = ConstructJWTAssertion(subId, subType, date.LocalDateTime.Add(delay)); } else { assertion = ConstructJWTAssertion(subId, subType, DateTimeOffset.UtcNow.Add(delay)); } Debug.WriteLine("HttpCode: {0}. Waiting for {1} seconds to retry JWT Authentication request.", ex.StatusCode, delay.Seconds); System.Threading.Tasks.Task.Delay(delay).Wait(); } else { throw ex; } } /**/ } }