public virtual Task <XboxLiveHttpResponse> GetResponseWithoutAuth(HttpCallResponseBodyType httpCallResponseBodyType)
        {
            if (!string.IsNullOrEmpty(this.ContractVersion))
            {
                this.SetCustomHeader("x-xbl-contract-version", this.ContractVersion);
            }

            foreach (KeyValuePair <string, string> customHeader in this.customHeaders)
            {
                this.webRequest.Headers[customHeader.Key] = customHeader.Value;
            }

            TaskCompletionSource <XboxLiveHttpResponse> getResponseCompletionSource = new TaskCompletionSource <XboxLiveHttpResponse>();

            this.WriteRequestBodyAsync().ContinueWith(writeBodyTask =>
            {
                // The explicit cast in the next method should not be necessary, but Visual Studio is complaining
                // that the call is ambiguous.  This removes that in-editor error.
                Task.Factory.FromAsync(this.webRequest.BeginGetResponse, (Func <IAsyncResult, WebResponse>) this.webRequest.EndGetResponse, null)
                .ContinueWith(getResponseTask =>
                {
                    if (getResponseTask.IsFaulted)
                    {
                        getResponseCompletionSource.SetException(getResponseTask.Exception);
                        return;
                    }

                    var response = new XboxLiveHttpResponse((HttpWebResponse)getResponseTask.Result, httpCallResponseBodyType);
                    getResponseCompletionSource.SetResult(response);
                });
            });

            return(getResponseCompletionSource.Task);
        }
예제 #2
0
        private Task <XboxLiveHttpResponse> HandleFastFail(HttpRetryAfterApiState apiState)
        {
            XboxLiveHttpResponse httpCallResponse = apiState.HttpCallResponse;

            this.RouteServiceCall(httpCallResponse);

            TaskCompletionSource <XboxLiveHttpResponse> taskCompletionSource = new TaskCompletionSource <XboxLiveHttpResponse>();

            taskCompletionSource.SetException(apiState.Exception);
            return(taskCompletionSource.Task);
        }
예제 #3
0
 private void RecordServiceResult(XboxLiveHttpResponse httpCallResponse, Exception exception)
 {
     // Only remember result if there was an error and there was a Retry-After header
     if (this.XboxLiveAPI != XboxLiveAPIName.Unspecified &&
         httpCallResponse.HttpStatus >= 400 //&&
         //httpCallResponse.RetryAfter.TotalSeconds > 0
         )
     {
         DateTime currentTime         = DateTime.UtcNow;
         HttpRetryAfterApiState state = new HttpRetryAfterApiState();
         state.RetryAfterTime   = currentTime + httpCallResponse.RetryAfter;
         state.HttpCallResponse = httpCallResponse;
         state.Exception        = exception;
         HttpRetryAfterManager.Instance.SetState(this.XboxLiveAPI, state);
     }
 }
예제 #4
0
        private void HandleThrottledCalls(XboxLiveHttpResponse httpCallResponse)
        {
            if (string.Equals(XboxLiveAppConfiguration.Instance.Sandbox, "RETAIL", StringComparison.Ordinal) ||
                this.contextSettings.AreAssertsForThrottlingInDevSandboxesDisabled)
            {
                return;
            }

#if DEBUG
            string msg;
            msg  = "Xbox Live service call to " + httpCallResponse.Url.ToString() + " was throttled\r\n";
            msg += httpCallResponse.RequestBody;
            msg += "\r\n";
            msg += "You can temporarily disable the assert by calling\r\n";
            msg += "XboxLive.Instance.Settings.DisableAssertsForXboxLiveThrottlingInDevSandboxes()\r\n";
            msg += "Note that this will only disable this assert.  You will still be throttled in all sandboxes.\r\n";
            Debug.WriteLine(msg);
#endif

            throw new XboxException("Xbox Live service call was throttled.  See Output for more detail");
        }
예제 #5
0
 private void RouteServiceCall(XboxLiveHttpResponse httpCallResponse)
 {
     // TODO: port route logic
 }
예제 #6
0
        private bool ShouldRetry(XboxLiveHttpResponse httpCallResponse)
        {
            int httpStatus = httpCallResponse.HttpStatus;

            if (!this.RetryAllowed &&
                !(httpStatus == (int)HttpStatusCode.Unauthorized && this.User != null))
            {
                return(false);
            }

            if ((httpStatus == (int)HttpStatusCode.Unauthorized && !this.hasPerformedRetryOn401) ||
                httpStatus == (int)HttpStatusCode.RequestTimeout ||
                httpStatus == HttpStatusCodeTooManyRequests ||
                httpStatus == (int)HttpStatusCode.InternalServerError ||
                httpStatus == (int)HttpStatusCode.BadGateway ||
                httpStatus == (int)HttpStatusCode.ServiceUnavailable ||
                httpStatus == (int)HttpStatusCode.GatewayTimeout ||
                httpCallResponse.NetworkFailure
                )
            {
                TimeSpan retryAfter = httpCallResponse.RetryAfter;

                // Compute how much time left before hitting the HttpTimeoutWindow setting.
                TimeSpan timeElapsedSinceFirstCall  = httpCallResponse.ResponseReceivedTime - this.firstCallStartTime;
                TimeSpan remainingTimeBeforeTimeout = this.contextSettings.HttpTimeoutWindow - timeElapsedSinceFirstCall;
                if (remainingTimeBeforeTimeout.TotalSeconds <= MinHttpTimeoutSeconds) // Need at least 5 seconds to bother making a call
                {
                    return(false);
                }

                // Based on the retry iteration, delay 2,4,8,16,etc seconds by default between retries
                // Jitter the response between the current and next delay based on system clock
                // Max wait time is 1 minute
                double   secondsToWaitMin   = Math.Pow(this.contextSettings.HttpRetryDelay.TotalSeconds, this.iterationNumber);
                double   secondsToWaitMax   = Math.Pow(this.contextSettings.HttpRetryDelay.TotalSeconds, this.iterationNumber + 1);
                double   secondsToWaitDelta = secondsToWaitMax - secondsToWaitMin;
                DateTime responseDate       = httpCallResponse.ResponseReceivedTime;
                double   randTime           =
                    (httpCallResponse.ResponseReceivedTime.Minute * 60.0 * 1000.0) +
                    (httpCallResponse.ResponseReceivedTime.Second * 1000.0) +
                    httpCallResponse.ResponseReceivedTime.Millisecond;
                double lerpScaler = (randTime % 10000) / 10000.0; // from 0 to 1 based on clock

                if (XboxLive.UseMockHttp)
                {
                    lerpScaler = 0; // make tests deterministic
                }

                double   secondsToWaitUncapped = secondsToWaitMin + secondsToWaitDelta * lerpScaler; // lerp between min & max wait
                double   secondsToWait         = Math.Min(secondsToWaitUncapped, MaxDelayTimeInSec); // cap max wait to 1 min
                TimeSpan waitTime = TimeSpan.FromSeconds(secondsToWait);
                if (retryAfter.TotalMilliseconds > 0)
                {
                    // Use either the waitTime or Retry-After header, whichever is bigger
                    this.delayBeforeRetry = (waitTime > retryAfter) ? waitTime : retryAfter;
                }
                else
                {
                    this.delayBeforeRetry = waitTime;
                }

                if (remainingTimeBeforeTimeout < this.delayBeforeRetry + TimeSpan.FromSeconds(MinHttpTimeoutSeconds))
                {
                    // Don't bother retrying when out of time
                    return(false);
                }

                if (httpStatus == (int)HttpStatusCode.InternalServerError)
                {
                    // For 500 - Internal Error, wait at least 10 seconds before retrying.
                    TimeSpan minDelayForHttpInternalError = TimeSpan.FromSeconds(MinDelayForHttpInternalErrorInSec);
                    if (this.delayBeforeRetry < minDelayForHttpInternalError)
                    {
                        this.delayBeforeRetry = minDelayForHttpInternalError;
                    }
                }
                else if (httpStatus == (int)HttpStatusCode.Unauthorized)
                {
                    return(this.HandleUnauthorizedError());
                }

                return(true);
            }

            return(false);
        }
예제 #7
0
        private Task <XboxLiveHttpResponse> InternalGetResponse()
        {
            DateTime requestStartTime = DateTime.UtcNow;

            if (this.iterationNumber == 0)
            {
                this.firstCallStartTime = requestStartTime;
            }
            this.iterationNumber++;

            HttpRetryAfterApiState apiState;

            if (HttpRetryAfterManager.Instance.GetState(this.XboxLiveAPI, out apiState))
            {
                if (this.ShouldFastFail(apiState, requestStartTime))
                {
                    return(this.HandleFastFail(apiState));
                }
                else
                {
                    HttpRetryAfterManager.Instance.ClearState(this.XboxLiveAPI);
                }
            }

            this.SetUserAgent();

            TaskCompletionSource <XboxLiveHttpResponse> taskCompletionSource = new TaskCompletionSource <XboxLiveHttpResponse>();

            this.WriteRequestBodyAsync().ContinueWith(writeBodyTask =>
            {
                // The explicit cast in the next method should not be necessary, but Visual Studio is complaining
                // that the call is ambiguous.  This removes that in-editor error.
                Task.Factory.FromAsync(this.webRequest.BeginGetResponse, (Func <IAsyncResult, WebResponse>) this.webRequest.EndGetResponse, null)
                .ContinueWith(getResponseTask =>
                {
                    var httpWebResponse = ExtractHttpWebResponse(getResponseTask);
                    int httpStatusCode  = 0;
                    bool networkFailure = false;
                    if (httpWebResponse != null)
                    {
                        httpStatusCode = (int)httpWebResponse.StatusCode;
                    }
                    else
                    {
                        // classify as network failure if there's no HTTP status code and didn't get response
                        networkFailure = getResponseTask.IsFaulted || getResponseTask.IsCanceled;
                    }

                    var httpCallResponse = new XboxLiveHttpResponse(
                        httpStatusCode,
                        networkFailure,
                        httpWebResponse,
                        DateTime.UtcNow,
                        requestStartTime,
                        this.User != null ? this.User.XboxUserId : "",
                        this.contextSettings,
                        this.Url,
                        this.XboxLiveAPI,
                        this.Method,
                        this.RequestBody,
                        this.ResponseBodyType
                        );

                    if (this.ShouldRetry(httpCallResponse))
                    {
                        // Wait and retry call
                        this.RecordServiceResult(httpCallResponse, getResponseTask.Exception);
                        this.RouteServiceCall(httpCallResponse);
                        Sleep(this.delayBeforeRetry);
                        this.webRequest = CloneHttpWebRequest(this.webRequest);
                        this.InternalGetResponse().ContinueWith(retryGetResponseTask =>
                        {
                            if (retryGetResponseTask.IsFaulted)
                            {
                                taskCompletionSource.SetException(retryGetResponseTask.Exception);
                            }
                            else
                            {
                                taskCompletionSource.SetResult(retryGetResponseTask.Result);
                            }
                        });
                    }
                    else if (!networkFailure) // Got HTTP status code
                    {
                        // HTTP 429: TOO MANY REQUESTS errors should return a JSON debug payload
                        // describing the details about why the call was throttled
                        this.RecordServiceResult(httpCallResponse, getResponseTask.Exception);
                        this.RouteServiceCall(httpCallResponse);

                        if (httpCallResponse.HttpStatus == HttpStatusCodeTooManyRequests)
                        {
                            this.HandleThrottledCalls(httpCallResponse);
                        }

                        if (getResponseTask.IsFaulted)
                        {
                            taskCompletionSource.SetException(getResponseTask.Exception);
                        }
                        else
                        {
                            taskCompletionSource.SetResult(httpCallResponse);
                        }
                    }
                    else
                    {
                        // Handle network errors

                        // HandleResponseError(); // TODO: extract error from JSON
                        this.RecordServiceResult(httpCallResponse, getResponseTask.Exception);
                        this.RouteServiceCall(httpCallResponse);
                        taskCompletionSource.SetException(getResponseTask.Exception);
                    }
                });
            });

            return(taskCompletionSource.Task);
        }