// ------------------------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. // ------------------------------------------------------------------------------ namespace Microsoft.Graph { using System; using System.Collections.Generic; using System.Diagnostics; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; /// /// A implementation that is used for simulating server failures. /// public class ChaosHandler : DelegatingHandler { private DiagnosticSource _logger = new DiagnosticListener("Microsoft.Graph.ChaosHandler"); private Random _random; private ChaosHandlerOption _globalChaosHandlerOptions; private List _KnownGraphFailures; /// /// Create a ChaosHandler. /// /// Optional parameter to change default behavior of handler. public ChaosHandler(ChaosHandlerOption chaosHandlerOptions = null) { _globalChaosHandlerOptions = chaosHandlerOptions ?? new ChaosHandlerOption(); _random = new Random(DateTime.Now.Millisecond); LoadKnownGraphFailures(_globalChaosHandlerOptions.KnownChaos); } /// /// Sends the request /// /// The request to send. /// The for the request. /// protected async override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // Select global or per request options var chaosHandlerOptions = GetPerRequestOptions(request) ??_globalChaosHandlerOptions; HttpResponseMessage response = null; // Planned Chaos or Random? if (chaosHandlerOptions.PlannedChaosFactory != null) { response = chaosHandlerOptions.PlannedChaosFactory(request); if (response != null) { response.RequestMessage = request; if (_logger.IsEnabled("PlannedChaosResponse")) _logger.Write("PlannedChaosResponse", response); } } else { if (_random.Next(100) < chaosHandlerOptions.ChaosPercentLevel) { response = CreateChaosResponse(chaosHandlerOptions.KnownChaos ?? _KnownGraphFailures); response.RequestMessage = request; if (_logger.IsEnabled("ChaosResponse")) _logger.Write("ChaosResponse", response); } } if (response == null) { response = await base.SendAsync(request, cancellationToken); } return response; } private ChaosHandlerOption GetPerRequestOptions(HttpRequestMessage request) { request.Properties.TryGetValue("ChaosRequestOptions", out var optionsObject); return (ChaosHandlerOption)optionsObject; } private HttpResponseMessage CreateChaosResponse(List knownFailures) { var responseIndex = _random.Next(knownFailures.Count); return knownFailures[responseIndex]; } private void LoadKnownGraphFailures(List knownFailures) { if (knownFailures != null && knownFailures.Count > 0) { _KnownGraphFailures = knownFailures; } else { _KnownGraphFailures = new List(); _KnownGraphFailures.Add(Create429TooManyRequestsResponse(new TimeSpan(0, 0, 3))); _KnownGraphFailures.Add(Create503Response(new TimeSpan(0, 0, 3))); _KnownGraphFailures.Add(Create504GatewayTimeoutResponse(new TimeSpan(0, 0, 3))); } } /// /// Create a HTTP status 429 response message /// /// for retry condition header value /// A object simulating a 429 response public static HttpResponseMessage Create429TooManyRequestsResponse(TimeSpan retry) { var serializer = new Serializer(); var throttleResponse = new HttpResponseMessage() { StatusCode = (HttpStatusCode)429, Content = serializer.SerializeAsJsonContent(new { error = new Error() { Code = "activityLimitReached", Message= "Client application has been throttled and should not attempt to repeat the request until an amount of time has elapsed." } }) }; throttleResponse.Headers.RetryAfter = new RetryConditionHeaderValue(retry); return throttleResponse; } /// /// Create a HTTP status 503 response message /// /// for retry condition header value /// A object simulating a 503 response public static HttpResponseMessage Create503Response(TimeSpan retry) { var serializer = new Serializer(); var serverUnavailableResponse = new HttpResponseMessage() { StatusCode = HttpStatusCode.ServiceUnavailable, Content = serializer.SerializeAsJsonContent(new { error = new Error() { Code = "serviceNotAvailable", Message= "The service is temporarily unavailable for maintenance or is overloaded. You may repeat the request after a delay, the length of which may be specified in a Retry-After header." } }) }; serverUnavailableResponse.Headers.RetryAfter = new RetryConditionHeaderValue(retry); return serverUnavailableResponse; } /// /// Create a HTTP status 502 response message /// /// A object simulating a 502 Response public static HttpResponseMessage Create502BadGatewayResponse() { var serializer = new Serializer(); var badGatewayResponse = new HttpResponseMessage() { StatusCode = HttpStatusCode.BadGateway, Content = serializer.SerializeAsJsonContent(new { error = new Error() { Code = "502" } }) }; return badGatewayResponse; } /// /// Create a HTTP status 500 response message /// /// A object simulating a 500 Response public static HttpResponseMessage Create500InternalServerErrorResponse() { var serializer = new Serializer(); var internalServerError = new HttpResponseMessage() { StatusCode = HttpStatusCode.InternalServerError, Content = serializer.SerializeAsJsonContent(new { error = new Error() { Code = "generalException", Message= "There was an internal server error while processing the request." } }) }; return internalServerError; } /// /// Create a HTTP status 504 response message /// /// for retry condition header value /// A object simulating a 504 response public static HttpResponseMessage Create504GatewayTimeoutResponse(TimeSpan retry) { var serializer = new Serializer(); var gatewayTimeoutResponse = new HttpResponseMessage() { StatusCode = HttpStatusCode.GatewayTimeout, Content = serializer.SerializeAsJsonContent(new { error = new Error() { Code = "504", Message = "The server, while acting as a proxy, did not receive a timely response from the upstream server it needed to access in attempting to complete the request. May occur together with 503." } }) }; gatewayTimeoutResponse.Headers.RetryAfter = new RetryConditionHeaderValue(retry); return gatewayTimeoutResponse; } } }