// ------------------------------------------------------------------------------
// 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;
}
}
}