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); }
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); }
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); } }
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"); }
private void RouteServiceCall(XboxLiveHttpResponse httpCallResponse) { // TODO: port route logic }
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); }
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); }