Contains event data for an error that could trigger a retry of a request.
상속: RiotNet.ResponseEventArgs
예제 #1
0
 /// <summary>
 /// Occurs when the server returns an error code of 500 or higher.
 /// </summary>
 /// <param name="e">Arguments for the event.</param>
 protected virtual void OnServerError(RetryEventArgs e)
 {
     ServerError?.Invoke(this, e);
 }
예제 #2
0
 /// <summary>
 /// Occurs when a request fails because the rate limit has been exceeded (status code 429).
 /// </summary>
 /// <param name="e">Arguments for the event.</param>
 protected virtual void OnRateLimitExceeded(RetryEventArgs e)
 {
     RateLimitExceeded?.Invoke(this, e);
 }
예제 #3
0
 /// <summary>
 /// Occurs when the client fails to connect to the server while executing a request.
 /// </summary>
 /// <param name="e">Arguments for the event.</param>
 protected virtual void OnConnectionFailed(RetryEventArgs e)
 {
     ConnectionFailed?.Invoke(this, e);
 }
예제 #4
0
 /// <summary>
 /// Occurs when a request times out.
 /// </summary>
 /// <param name="e">Arguments for the event.</param>
 protected virtual void OnRequestTimedOut(RetryEventArgs e)
 {
     RequestTimedOut?.Invoke(this, e);
 }
예제 #5
0
        /// <summary>
        /// Determines which action to take for the given response.
        /// </summary>
        /// <param name="response">An <see cref="HttpResponseMessage"/>.</param>
        /// <param name="attemptCount">The number of times the request has been attempted so far.</param>
        /// <param name="token">The cancellation token to cancel the operation.</param>
        /// <returns>A <see cref="ResponseAction"/>.</returns>
        protected virtual async Task <ResponseAction> DetermineResponseAction(RiotResponse response, int attemptCount, CancellationToken token)
        {
            if (response.TimedOut)
            {
                var args = new RetryEventArgs(response, attemptCount)
                {
                    Retry = Settings.RetryOnTimeout
                };
                OnRequestTimedOut(args);
                // Note: never retry if the token is cancelled. It will certainly fail the next time, too.
                if (args.Retry && !token.IsCancellationRequested)
                {
                    return(ResponseAction.Retry);
                }
                if (Settings.ThrowOnError)
                {
                    throw new RestTimeoutException(response, response.Exception);
                }

                return(ResponseAction.ReturnDefault);
            }
            if (response.Response == null)
            {
                var args = new RetryEventArgs(response, attemptCount)
                {
                    Retry = Settings.RetryOnConnectionFailure
                };
                OnConnectionFailed(args);
                if (args.Retry)
                {
                    return(ResponseAction.Retry);
                }
                if (Settings.ThrowOnError)
                {
                    throw new ConnectionFailedException(response, response.Exception);
                }

                return(ResponseAction.ReturnDefault);
            }
            var statusCode = (int)response.Response.StatusCode;

            if (statusCode == 429)
            {
                var args = new RetryEventArgs(response, attemptCount)
                {
                    Retry = Settings.RetryOnRateLimitExceeded
                };
                OnRateLimitExceeded(args);
                if (args.Retry)
                {
                    return(ResponseAction.Retry);
                }
                if (Settings.ThrowOnError)
                {
                    throw new RateLimitExceededException(response);
                }

                return(ResponseAction.ReturnDefault);
            }
            if (statusCode == 404)
            {
                OnResourceNotFound(new ResponseEventArgs(response));
                if (Settings.ThrowOnNotFound)
                {
                    throw new NotFoundException(response);
                }

                return(ResponseAction.ReturnDefault);
            }
            if (statusCode >= 500)
            {
                var args = new RetryEventArgs(response, attemptCount)
                {
                    Retry = Settings.RetryOnServerError
                };
                OnServerError(args);
                if (args.Retry)
                {
                    return(ResponseAction.Retry);
                }
                if (Settings.ThrowOnError)
                {
                    var message = await GetServerErrorMessage(response).ConfigureAwait(false);

                    if (message != null)
                    {
                        throw new RestException(response, message);
                    }
                    else
                    {
                        throw new RestException(response);
                    }
                }

                return(ResponseAction.ReturnDefault);
            }
            if (statusCode >= 400)
            {
                var message = await GetServerErrorMessage(response).ConfigureAwait(false);

                OnResponseError(new ResponseEventArgs(response, message));
                if (Settings.ThrowOnError)
                {
                    if (message != null)
                    {
                        throw new RestException(response, message, response.Exception);
                    }
                    else
                    {
                        throw new RestException(response, response.Exception);
                    }
                }

                return(ResponseAction.ReturnDefault);
            }
            return(ResponseAction.Return);
        }
예제 #6
0
        /// <summary>
        /// Sends a request.
        /// </summary>
        /// <param name="request">The request to send.</param>
        /// <param name="methodName">The name of the method being executed (for rate limiting purposes).</param>
        /// <param name="platformId">The platform ID corresponding to the server. This should equal one of the <see cref="Models.PlatformId"/> values.</param>
        /// <param name="token">The cancellation token to cancel the operation.</param>
        /// <returns>A task that represents the asynchronous operation.</returns>
        protected async Task <RiotResponse> SendAsync(HttpRequestMessage request, string methodName, string platformId, CancellationToken token)
        {
            DateTime targetTime = DateTime.UtcNow;

            if (retryAfterTimes.TryGetValue(platformId, out DateTime retryAfter) && retryAfter > targetTime)
            {
                targetTime = retryAfter;
            }
            if (rateLimiter != null)
            {
                var limiterDelayTime = rateLimiter.AddRequestOrGetDelay(methodName, platformId);
                if (limiterDelayTime > targetTime)
                {
                    targetTime = limiterDelayTime;
                }
            }
            if (targetTime <= DateTime.UtcNow)
            {
                try
                {
                    var response = await client.SendAsync(request, token).ConfigureAwait(false);

                    return(new RiotResponse(response));
                }
                catch (TaskCanceledException ex)
                {
                    return(new RiotResponse(null, ex, true));
                }
                catch (Exception ex)
                {
                    return(new RiotResponse(null, ex));
                }
            }
            else
            {
                // We need to throttle. Add requests to a queue to ensure that they are processed in order, so one request doesn't get starved.
                var args = new RetryEventArgs(null, 1)
                {
                    Retry = Settings.RetryOnRateLimitExceeded
                };
                OnRateLimitExceeded(args);
                if (!args.Retry)
                {
                    if (Settings.ThrowOnError)
                    {
                        throw new RateLimitExceededException();
                    }

                    return(null);
                }

                // Create a task to re-send but DON'T start it until targetTime.
                var task = new Task <Task <RiotResponse> >(() => SendAsync(request, methodName, platformId, token), token);
                ConcurrentQueue <Task <Task <RiotResponse> > > addedQueue = null;
                var queue = throttledRequestQueues.GetOrAdd(targetTime, (dt) =>
                {
                    return(addedQueue = new ConcurrentQueue <Task <Task <RiotResponse> > >());
                });
                queue.Enqueue(task);
                // Checking that addedQueue == queue is the only way to guarantee that the new queue was really added.
                // According to Microsoft docs: "If you call GetOrAdd simultaneously on different threads, addValueFactory may be called multiple times,
                // but its key/value pair might not be added to the dictionary for every call."
                if (addedQueue == queue)
                {
                    ProcessRequestQueue(targetTime);
                }

                return(await await task);
            }
        }