Beispiel #1
0
        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;
            }
        }
Beispiel #4
0
        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;
                    }
                } /**/
            }
        }