mirror of
				https://github.com/nsnail/Ocelot.git
				synced 2025-11-05 00:10:49 +08:00 
			
		
		
		
	Merge pull request #16 from TomPallister/develop
refactor: HttpClientCache
This commit is contained in:
		@@ -1 +1 @@
 | 
			
		||||
{"ReRoutes":[],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":"/administration"}}
 | 
			
		||||
{"ReRoutes":[{"DownstreamPathTemplate":"/","UpstreamPathTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":51879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"ClientWhitelist":[],"EnableRateLimiting":false,"Period":null,"PeriodTimespan":0.0,"Limit":0}}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":null,"RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false,"HttpStatusCode":429}}}
 | 
			
		||||
@@ -143,6 +143,7 @@ namespace Ocelot.DependencyInjection
 | 
			
		||||
            services.AddSingleton<IAuthenticationHandlerFactory, AuthenticationHandlerFactory>();
 | 
			
		||||
            services.AddSingleton<IAuthenticationHandlerCreator, AuthenticationHandlerCreator>();
 | 
			
		||||
            services.AddSingleton<IRateLimitCounterHandler, MemoryCacheRateLimitCounterHandler>();
 | 
			
		||||
            services.AddSingleton<IHttpClientCache, MemoryHttpClientCache>();
 | 
			
		||||
 | 
			
		||||
            // see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc
 | 
			
		||||
            // could maybe use a scoped data repository
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,6 @@ namespace Ocelot.Request.Builder
 | 
			
		||||
            string downstreamUrl, 
 | 
			
		||||
            Stream content, 
 | 
			
		||||
            IHeaderDictionary headers,
 | 
			
		||||
            IRequestCookieCollection cookies, 
 | 
			
		||||
            QueryString queryString, 
 | 
			
		||||
            string contentType, 
 | 
			
		||||
            RequestId.RequestId requestId,
 | 
			
		||||
@@ -29,7 +28,6 @@ namespace Ocelot.Request.Builder
 | 
			
		||||
                .WithContentType(contentType)
 | 
			
		||||
                .WithHeaders(headers)
 | 
			
		||||
                .WithRequestId(requestId)
 | 
			
		||||
                .WithCookies(cookies)
 | 
			
		||||
                .WithIsQos(isQos)
 | 
			
		||||
                .WithQos(qosProvider)
 | 
			
		||||
                .Build();
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,6 @@ namespace Ocelot.Request.Builder
 | 
			
		||||
            string downstreamUrl,
 | 
			
		||||
            Stream content,
 | 
			
		||||
            IHeaderDictionary headers,
 | 
			
		||||
            IRequestCookieCollection cookies,
 | 
			
		||||
            QueryString queryString,
 | 
			
		||||
            string contentType,
 | 
			
		||||
            RequestId.RequestId requestId,
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,6 @@ namespace Ocelot.Request.Builder
 | 
			
		||||
        private string _contentType;
 | 
			
		||||
        private IHeaderDictionary _headers;
 | 
			
		||||
        private RequestId.RequestId _requestId;
 | 
			
		||||
        private IRequestCookieCollection _cookies;
 | 
			
		||||
        private readonly string[] _unsupportedHeaders = {"host"};
 | 
			
		||||
        private bool _isQos;
 | 
			
		||||
        private IQoSProvider _qoSProvider;
 | 
			
		||||
@@ -68,12 +67,6 @@ namespace Ocelot.Request.Builder
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public RequestBuilder WithCookies(IRequestCookieCollection cookies)
 | 
			
		||||
        {
 | 
			
		||||
            _cookies = cookies;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public RequestBuilder WithIsQos(bool isqos)
 | 
			
		||||
        {
 | 
			
		||||
            _isQos = isqos;
 | 
			
		||||
@@ -103,9 +96,7 @@ namespace Ocelot.Request.Builder
 | 
			
		||||
                AddRequestIdHeader(_requestId, httpRequestMessage);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var cookieContainer = CreateCookieContainer(uri);
 | 
			
		||||
 | 
			
		||||
            return new Request(httpRequestMessage, cookieContainer,_isQos, _qoSProvider);
 | 
			
		||||
            return new Request(httpRequestMessage,_isQos, _qoSProvider);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Uri CreateUri()
 | 
			
		||||
@@ -153,21 +144,6 @@ namespace Ocelot.Request.Builder
 | 
			
		||||
            return !_unsupportedHeaders.Contains(header.Key.ToLower());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private CookieContainer CreateCookieContainer(Uri uri)
 | 
			
		||||
        {
 | 
			
		||||
            var cookieContainer = new CookieContainer();
 | 
			
		||||
 | 
			
		||||
            if (_cookies != null)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var cookie in _cookies)
 | 
			
		||||
                {
 | 
			
		||||
                    cookieContainer.Add(uri, new Cookie(cookie.Key, cookie.Value));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return cookieContainer;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void AddRequestIdHeader(RequestId.RequestId requestId, HttpRequestMessage httpRequestMessage)
 | 
			
		||||
        {
 | 
			
		||||
            httpRequestMessage.Headers.Add(requestId.RequestIdKey, requestId.RequestIdValue);
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,6 @@ namespace Ocelot.Request.Middleware
 | 
			
		||||
                    DownstreamUrl,
 | 
			
		||||
                    context.Request.Body,
 | 
			
		||||
                    context.Request.Headers,
 | 
			
		||||
                    context.Request.Cookies,
 | 
			
		||||
                    context.Request.QueryString,
 | 
			
		||||
                    context.Request.ContentType,
 | 
			
		||||
                    new RequestId.RequestId(DownstreamRoute?.ReRoute?.RequestIdKey, context.TraceIdentifier),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,4 @@
 | 
			
		||||
using Ocelot.Configuration;
 | 
			
		||||
using Ocelot.Values;
 | 
			
		||||
using System.Net;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using Ocelot.Requester.QoS;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Request
 | 
			
		||||
@@ -10,18 +7,15 @@ namespace Ocelot.Request
 | 
			
		||||
    {
 | 
			
		||||
        public Request(
 | 
			
		||||
            HttpRequestMessage httpRequestMessage, 
 | 
			
		||||
            CookieContainer cookieContainer,
 | 
			
		||||
            bool isQos,
 | 
			
		||||
            IQoSProvider qosProvider)
 | 
			
		||||
        {
 | 
			
		||||
            HttpRequestMessage = httpRequestMessage;
 | 
			
		||||
            CookieContainer = cookieContainer;
 | 
			
		||||
            IsQos = isQos;
 | 
			
		||||
            QosProvider = qosProvider;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public HttpRequestMessage HttpRequestMessage { get; private set; }
 | 
			
		||||
        public CookieContainer CookieContainer { get; private set; }
 | 
			
		||||
        public bool IsQos { get; private set; }
 | 
			
		||||
        public IQoSProvider QosProvider { get; private set; }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,46 +1,67 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Ocelot.Logging;
 | 
			
		||||
using Ocelot.Requester.QoS;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Requester
 | 
			
		||||
{
 | 
			
		||||
    internal class HttpClientBuilder
 | 
			
		||||
    internal class HttpClientBuilder : IHttpClientBuilder
 | 
			
		||||
    {
 | 
			
		||||
        private readonly Dictionary<int, Func<DelegatingHandler>> _handlers = new Dictionary<int, Func<DelegatingHandler>>();
 | 
			
		||||
 | 
			
		||||
        public HttpClientBuilder WithQoS(IQoSProvider qoSProvider, IOcelotLogger logger, HttpMessageHandler innerHandler)
 | 
			
		||||
        public  IHttpClientBuilder WithQos(IQoSProvider qosProvider, IOcelotLogger logger)
 | 
			
		||||
        {
 | 
			
		||||
            _handlers.Add(5000, () => new PollyCircuitBreakingDelegatingHandler(qoSProvider, logger, innerHandler));
 | 
			
		||||
            _handlers.Add(5000, () => new PollyCircuitBreakingDelegatingHandler(qosProvider, logger));
 | 
			
		||||
 | 
			
		||||
            return this;
 | 
			
		||||
        }  
 | 
			
		||||
 | 
			
		||||
        public IHttpClient Create()
 | 
			
		||||
        {
 | 
			
		||||
            var httpclientHandler = new HttpClientHandler();
 | 
			
		||||
            
 | 
			
		||||
            var client = new HttpClient(CreateHttpMessageHandler(httpclientHandler));                
 | 
			
		||||
            
 | 
			
		||||
            return new HttpClientWrapper(client);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        internal HttpClient Build(HttpMessageHandler innerHandler)
 | 
			
		||||
        {
 | 
			
		||||
            return _handlers.Any() ? 
 | 
			
		||||
                new HttpClient(CreateHttpMessageHandler()) : 
 | 
			
		||||
                new HttpClient(innerHandler);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private HttpMessageHandler CreateHttpMessageHandler()
 | 
			
		||||
        {
 | 
			
		||||
            HttpMessageHandler httpMessageHandler = new HttpClientHandler();
 | 
			
		||||
 | 
			
		||||
        private HttpMessageHandler CreateHttpMessageHandler(HttpMessageHandler httpMessageHandler)
 | 
			
		||||
        {            
 | 
			
		||||
   
 | 
			
		||||
            _handlers
 | 
			
		||||
                .OrderByDescending(handler => handler.Key)
 | 
			
		||||
                .Select(handler => handler.Value)
 | 
			
		||||
                .Reverse()
 | 
			
		||||
                .ToList()
 | 
			
		||||
                .ForEach(handler =>
 | 
			
		||||
            {
 | 
			
		||||
                var delegatingHandler = handler();
 | 
			
		||||
                delegatingHandler.InnerHandler = httpMessageHandler;
 | 
			
		||||
                httpMessageHandler = delegatingHandler;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
                {
 | 
			
		||||
                    var delegatingHandler = handler();
 | 
			
		||||
                    delegatingHandler.InnerHandler = httpMessageHandler;
 | 
			
		||||
                    httpMessageHandler = delegatingHandler;
 | 
			
		||||
                });
 | 
			
		||||
            return httpMessageHandler;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// This class was made to make unit testing easier when HttpClient is used.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    internal class HttpClientWrapper : IHttpClient
 | 
			
		||||
    {
 | 
			
		||||
        public HttpClient Client { get; }
 | 
			
		||||
 | 
			
		||||
        public HttpClientWrapper(HttpClient client)
 | 
			
		||||
        {
 | 
			
		||||
            Client = client;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
 | 
			
		||||
        {
 | 
			
		||||
            return Client.SendAsync(request);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Ocelot.Logging;
 | 
			
		||||
@@ -10,47 +11,61 @@ namespace Ocelot.Requester
 | 
			
		||||
{
 | 
			
		||||
    public class HttpClientHttpRequester : IHttpRequester
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IHttpClientCache _cacheHandlers;
 | 
			
		||||
        private readonly IOcelotLogger _logger;
 | 
			
		||||
 | 
			
		||||
        public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory)
 | 
			
		||||
        public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory, IHttpClientCache cacheHandlers)
 | 
			
		||||
        {
 | 
			
		||||
            _logger = loggerFactory.CreateLogger<HttpClientHttpRequester>();
 | 
			
		||||
            _cacheHandlers = cacheHandlers;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<Response<HttpResponseMessage>> GetResponse(Request.Request request)
 | 
			
		||||
        {
 | 
			
		||||
            var builder = new HttpClientBuilder();    
 | 
			
		||||
            var builder = new HttpClientBuilder();
 | 
			
		||||
 | 
			
		||||
            using (var handler = new HttpClientHandler { CookieContainer = request.CookieContainer })
 | 
			
		||||
            var cacheKey = GetCacheKey(request, builder);
 | 
			
		||||
 | 
			
		||||
            var httpClient = _cacheHandlers.Get(cacheKey);
 | 
			
		||||
            if (httpClient == null)
 | 
			
		||||
            {
 | 
			
		||||
                if (request.IsQos)
 | 
			
		||||
                {
 | 
			
		||||
                    builder.WithQoS(request.QosProvider, _logger, handler);
 | 
			
		||||
                }           
 | 
			
		||||
 | 
			
		||||
                using (var httpClient = builder.Build(handler))
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        var response = await httpClient.SendAsync(request.HttpRequestMessage);
 | 
			
		||||
                        return new OkResponse<HttpResponseMessage>(response);
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (TimeoutRejectedException exception)
 | 
			
		||||
                    {
 | 
			
		||||
                        return
 | 
			
		||||
                            new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (BrokenCircuitException exception)
 | 
			
		||||
                    {
 | 
			
		||||
                        return
 | 
			
		||||
                            new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (Exception exception)
 | 
			
		||||
                    {
 | 
			
		||||
                        return new ErrorResponse<HttpResponseMessage>(new UnableToCompleteRequestError(exception));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                httpClient = builder.Create();
 | 
			
		||||
                _cacheHandlers.Set(cacheKey, httpClient, TimeSpan.FromHours(6));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var response = await httpClient.SendAsync(request.HttpRequestMessage);
 | 
			
		||||
                return new OkResponse<HttpResponseMessage>(response);
 | 
			
		||||
            }
 | 
			
		||||
            catch (TimeoutRejectedException exception)
 | 
			
		||||
            {
 | 
			
		||||
                return
 | 
			
		||||
                    new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
 | 
			
		||||
            }
 | 
			
		||||
            catch (BrokenCircuitException exception)
 | 
			
		||||
            {
 | 
			
		||||
                return
 | 
			
		||||
                    new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception exception)
 | 
			
		||||
            {
 | 
			
		||||
                return new ErrorResponse<HttpResponseMessage>(new UnableToCompleteRequestError(exception));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private string GetCacheKey(Request.Request request, IHttpClientBuilder builder)
 | 
			
		||||
        {
 | 
			
		||||
            string baseUrl = $"{request.HttpRequestMessage.RequestUri.Scheme}://{request.HttpRequestMessage.RequestUri.Authority}";
 | 
			
		||||
 | 
			
		||||
            if (request.IsQos)
 | 
			
		||||
            {
 | 
			
		||||
                builder.WithQos(request.QosProvider, _logger);
 | 
			
		||||
                baseUrl = $"{baseUrl}{request.QosProvider.CircuitBreaker.CircuitBreakerPolicy.PolicyKey}";
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return baseUrl;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								src/Ocelot/Requester/IHttpClient.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/Ocelot/Requester/IHttpClient.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Requester
 | 
			
		||||
{
 | 
			
		||||
    public interface IHttpClient
 | 
			
		||||
    {
 | 
			
		||||
        HttpClient Client { get; }
 | 
			
		||||
 | 
			
		||||
        Task<HttpResponseMessage> SendAsync(HttpRequestMessage request);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								src/Ocelot/Requester/IHttpClientBuilder.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/Ocelot/Requester/IHttpClientBuilder.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Ocelot.Logging;
 | 
			
		||||
using Ocelot.Requester.QoS;
 | 
			
		||||
using System.Net;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Requester
 | 
			
		||||
{
 | 
			
		||||
    public interface IHttpClientBuilder
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Sets a PollyCircuitBreakingDelegatingHandler .
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        IHttpClientBuilder WithQos(IQoSProvider qosProvider, IOcelotLogger logger);            
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates the <see cref="HttpClient"/>
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        IHttpClient Create();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								src/Ocelot/Requester/IHttpClientCache.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/Ocelot/Requester/IHttpClientCache.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Requester
 | 
			
		||||
{
 | 
			
		||||
    public interface IHttpClientCache
 | 
			
		||||
    {
 | 
			
		||||
        bool Exists(string id);
 | 
			
		||||
        IHttpClient Get(string id);
 | 
			
		||||
        void Remove(string id);
 | 
			
		||||
        void Set(string id, IHttpClient handler, TimeSpan expirationTime);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								src/Ocelot/Requester/MemoryHttpClientCache.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/Ocelot/Requester/MemoryHttpClientCache.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
using Microsoft.Extensions.Caching.Memory;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Requester
 | 
			
		||||
{
 | 
			
		||||
    public class MemoryHttpClientCache : IHttpClientCache
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IMemoryCache _memoryCache;
 | 
			
		||||
 | 
			
		||||
        public MemoryHttpClientCache(IMemoryCache memoryCache)
 | 
			
		||||
        {
 | 
			
		||||
            _memoryCache = memoryCache;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Set(string id, IHttpClient client, TimeSpan expirationTime)
 | 
			
		||||
        {
 | 
			
		||||
            _memoryCache.Set(id, client, new MemoryCacheEntryOptions().SetAbsoluteExpiration(expirationTime));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool Exists(string id)
 | 
			
		||||
        {
 | 
			
		||||
            IHttpClient counter;
 | 
			
		||||
            return _memoryCache.TryGetValue(id, out counter);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IHttpClient Get(string id)
 | 
			
		||||
        {
 | 
			
		||||
            IHttpClient counter;
 | 
			
		||||
            if (_memoryCache.TryGetValue(id, out counter))
 | 
			
		||||
            {
 | 
			
		||||
                return counter;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Remove(string id)
 | 
			
		||||
        {
 | 
			
		||||
            _memoryCache.Remove(id);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -16,9 +16,7 @@ namespace Ocelot.Requester
 | 
			
		||||
 | 
			
		||||
        public PollyCircuitBreakingDelegatingHandler(
 | 
			
		||||
            IQoSProvider qoSProvider,
 | 
			
		||||
            IOcelotLogger logger, 
 | 
			
		||||
            HttpMessageHandler innerHandler)
 | 
			
		||||
            : base(innerHandler)
 | 
			
		||||
            IOcelotLogger logger)
 | 
			
		||||
        {
 | 
			
		||||
            _qoSProvider = qoSProvider;
 | 
			
		||||
            _logger = logger;
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ using Shouldly;
 | 
			
		||||
using TestStack.BDDfy;
 | 
			
		||||
using Xunit;
 | 
			
		||||
 | 
			
		||||
[assembly: CollectionBehavior(DisableTestParallelization = true)]
 | 
			
		||||
namespace Ocelot.IntegrationTests
 | 
			
		||||
{
 | 
			
		||||
    public class AdministrationTests : IDisposable
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										190
									
								
								test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,190 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Net;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Net.Http.Headers;
 | 
			
		||||
using Microsoft.AspNetCore.Builder;
 | 
			
		||||
using Microsoft.AspNetCore.Hosting;
 | 
			
		||||
using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using Ocelot.Configuration.File;
 | 
			
		||||
using Ocelot.ManualTest;
 | 
			
		||||
using Shouldly;
 | 
			
		||||
using TestStack.BDDfy;
 | 
			
		||||
using Xunit;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.IntegrationTests
 | 
			
		||||
{
 | 
			
		||||
    public class ThreadSafeHeadersTests : IDisposable
 | 
			
		||||
    {
 | 
			
		||||
        private readonly HttpClient _httpClient;
 | 
			
		||||
        private HttpResponseMessage _response;
 | 
			
		||||
        private IWebHost _builder;
 | 
			
		||||
        private IWebHostBuilder _webHostBuilder;
 | 
			
		||||
        private readonly string _ocelotBaseUrl;
 | 
			
		||||
        private BearerToken _token;
 | 
			
		||||
        private IWebHost _downstreamBuilder;
 | 
			
		||||
        private readonly Random _random;
 | 
			
		||||
        private readonly ConcurrentBag<ThreadSafeHeadersTestResult> _results;
 | 
			
		||||
 | 
			
		||||
        public ThreadSafeHeadersTests()
 | 
			
		||||
        {
 | 
			
		||||
            _results = new ConcurrentBag<ThreadSafeHeadersTestResult>();
 | 
			
		||||
            _random = new Random();
 | 
			
		||||
            _httpClient = new HttpClient();
 | 
			
		||||
            _ocelotBaseUrl = "http://localhost:5001";
 | 
			
		||||
            _httpClient.BaseAddress = new Uri(_ocelotBaseUrl);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void should_return_same_response_for_each_different_header_under_load_to_downsteam_service()
 | 
			
		||||
        {
 | 
			
		||||
            var configuration = new FileConfiguration
 | 
			
		||||
            {
 | 
			
		||||
                ReRoutes = new List<FileReRoute>
 | 
			
		||||
                    {
 | 
			
		||||
                        new FileReRoute
 | 
			
		||||
                        {
 | 
			
		||||
                            DownstreamPathTemplate = "/",
 | 
			
		||||
                            DownstreamScheme = "http",
 | 
			
		||||
                            DownstreamHost = "localhost",
 | 
			
		||||
                            DownstreamPort = 51879,
 | 
			
		||||
                            UpstreamPathTemplate = "/",
 | 
			
		||||
                            UpstreamHttpMethod = "Get",
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            this.Given(x => GivenThereIsAConfiguration(configuration))
 | 
			
		||||
               .And(x => GivenThereIsAServiceRunningOn("http://localhost:51879"))
 | 
			
		||||
                .And(x => GivenOcelotIsRunning())
 | 
			
		||||
                .When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 300))
 | 
			
		||||
                .Then(x => ThenTheSameHeaderValuesAreReturnedByTheDownstreamService())
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void GivenThereIsAServiceRunningOn(string url)
 | 
			
		||||
        {
 | 
			
		||||
            _downstreamBuilder = new WebHostBuilder()
 | 
			
		||||
                .UseUrls(url)
 | 
			
		||||
                .UseKestrel()
 | 
			
		||||
                .UseContentRoot(Directory.GetCurrentDirectory())
 | 
			
		||||
                .UseIISIntegration()
 | 
			
		||||
                .UseUrls(url)
 | 
			
		||||
                .Configure(app =>
 | 
			
		||||
                {
 | 
			
		||||
                    app.Run(async context =>
 | 
			
		||||
                    {
 | 
			
		||||
                        var header = context.Request.Headers["ThreadSafeHeadersTest"];
 | 
			
		||||
 | 
			
		||||
                        context.Response.StatusCode = 200;
 | 
			
		||||
                        await context.Response.WriteAsync(header[0]);
 | 
			
		||||
                    });
 | 
			
		||||
                })
 | 
			
		||||
                .Build();
 | 
			
		||||
 | 
			
		||||
            _downstreamBuilder.Start();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void GivenOcelotIsRunning()
 | 
			
		||||
        {
 | 
			
		||||
            _webHostBuilder = new WebHostBuilder()
 | 
			
		||||
                .UseUrls(_ocelotBaseUrl)
 | 
			
		||||
                .UseKestrel()
 | 
			
		||||
                .UseContentRoot(Directory.GetCurrentDirectory())
 | 
			
		||||
                .ConfigureServices(x =>
 | 
			
		||||
                {
 | 
			
		||||
                    x.AddSingleton(_webHostBuilder);
 | 
			
		||||
                })
 | 
			
		||||
                .UseStartup<Startup>();
 | 
			
		||||
 | 
			
		||||
            _builder = _webHostBuilder.Build();
 | 
			
		||||
 | 
			
		||||
            _builder.Start();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration)
 | 
			
		||||
        {
 | 
			
		||||
            var configurationPath = $"{Directory.GetCurrentDirectory()}/configuration.json";
 | 
			
		||||
 | 
			
		||||
            var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration);
 | 
			
		||||
 | 
			
		||||
            if (File.Exists(configurationPath))
 | 
			
		||||
            {
 | 
			
		||||
                File.Delete(configurationPath);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            File.WriteAllText(configurationPath, jsonConfiguration);
 | 
			
		||||
 | 
			
		||||
            var text = File.ReadAllText(configurationPath);
 | 
			
		||||
 | 
			
		||||
            configurationPath = $"{AppContext.BaseDirectory}/configuration.json";
 | 
			
		||||
 | 
			
		||||
            if (File.Exists(configurationPath))
 | 
			
		||||
            {
 | 
			
		||||
                File.Delete(configurationPath);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            File.WriteAllText(configurationPath, jsonConfiguration);
 | 
			
		||||
 | 
			
		||||
            text = File.ReadAllText(configurationPath);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues(string url, int times)
 | 
			
		||||
        {
 | 
			
		||||
            var tasks = new Task[times];
 | 
			
		||||
 | 
			
		||||
            for (int i = 0; i < times; i++)
 | 
			
		||||
            {
 | 
			
		||||
                var urlCopy = url;
 | 
			
		||||
                var random = _random.Next(0, 50);
 | 
			
		||||
                tasks[i] = GetForThreadSafeHeadersTest(urlCopy, random);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Task.WaitAll(tasks);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task GetForThreadSafeHeadersTest(string url, int random)
 | 
			
		||||
        {
 | 
			
		||||
            var request = new HttpRequestMessage(HttpMethod.Get, url);
 | 
			
		||||
            request.Headers.Add("ThreadSafeHeadersTest", new List<string> { random.ToString() });
 | 
			
		||||
            var response = await _httpClient.SendAsync(request);
 | 
			
		||||
            var content = await response.Content.ReadAsStringAsync();
 | 
			
		||||
            int result = int.Parse(content);
 | 
			
		||||
            var tshtr = new ThreadSafeHeadersTestResult(result, random);
 | 
			
		||||
            _results.Add(tshtr);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ThenTheSameHeaderValuesAreReturnedByTheDownstreamService()
 | 
			
		||||
        {
 | 
			
		||||
            foreach(var result in _results)
 | 
			
		||||
            {
 | 
			
		||||
                result.Result.ShouldBe(result.Random);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        public void Dispose()
 | 
			
		||||
        {
 | 
			
		||||
            _builder?.Dispose();
 | 
			
		||||
            _httpClient?.Dispose();
 | 
			
		||||
            _downstreamBuilder?.Dispose();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        class ThreadSafeHeadersTestResult
 | 
			
		||||
        {
 | 
			
		||||
            public ThreadSafeHeadersTestResult(int result, int random)
 | 
			
		||||
            {
 | 
			
		||||
                Result = result;
 | 
			
		||||
                Random = random;
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public int Result { get; private set; }
 | 
			
		||||
            public int Random { get; private set; }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
{"ReRoutes":[],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":"/administration","RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false,"HttpStatusCode":429}}}
 | 
			
		||||
{"ReRoutes":[{"DownstreamPathTemplate":"/","UpstreamPathTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":51879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"ClientWhitelist":[],"EnableRateLimiting":false,"Period":null,"PeriodTimespan":0.0,"Limit":0}}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":null,"RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false,"HttpStatusCode":429}}}
 | 
			
		||||
@@ -1,303 +1,311 @@
 | 
			
		||||
{
 | 
			
		||||
  "ReRoutes": [
 | 
			
		||||
    {
 | 
			
		||||
      "DownstreamPathTemplate": "/",
 | 
			
		||||
      "DownstreamScheme": "http",
 | 
			
		||||
      "DownstreamHost": "localhost",
 | 
			
		||||
      "DownstreamPort": 52876,
 | 
			
		||||
      "UpstreamPathTemplate": "/identityserverexample",
 | 
			
		||||
      "UpstreamHttpMethod": "Get",
 | 
			
		||||
      "QoSOptions": {
 | 
			
		||||
        "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
        "DurationOfBreak": 10,
 | 
			
		||||
        "TimeoutValue": 5000
 | 
			
		||||
      },
 | 
			
		||||
      "AuthenticationOptions": {
 | 
			
		||||
        "Provider": "IdentityServer",
 | 
			
		||||
        "ProviderRootUrl": "http://localhost:52888",
 | 
			
		||||
        "ScopeName": "api",
 | 
			
		||||
        "AdditionalScopes": [
 | 
			
		||||
          "openid",
 | 
			
		||||
          "offline_access"
 | 
			
		||||
        ],
 | 
			
		||||
        "ScopeSecret": "secret"
 | 
			
		||||
      },
 | 
			
		||||
      "AddHeadersToRequest": {
 | 
			
		||||
        "CustomerId": "Claims[CustomerId] > value",
 | 
			
		||||
        "LocationId": "Claims[LocationId] > value",
 | 
			
		||||
        "UserType": "Claims[sub] > value[0] > |",
 | 
			
		||||
        "UserId": "Claims[sub] > value[1] > |"
 | 
			
		||||
      },
 | 
			
		||||
      "AddClaimsToRequest": {
 | 
			
		||||
        "CustomerId": "Claims[CustomerId] > value",
 | 
			
		||||
        "LocationId": "Claims[LocationId] > value",
 | 
			
		||||
        "UserType": "Claims[sub] > value[0] > |",
 | 
			
		||||
        "UserId": "Claims[sub] > value[1] > |"
 | 
			
		||||
      },
 | 
			
		||||
      "AddQueriesToRequest": {
 | 
			
		||||
        "CustomerId": "Claims[CustomerId] > value",
 | 
			
		||||
        "LocationId": "Claims[LocationId] > value",
 | 
			
		||||
        "UserType": "Claims[sub] > value[0] > |",
 | 
			
		||||
        "UserId": "Claims[sub] > value[1] > |"
 | 
			
		||||
      },
 | 
			
		||||
      "RouteClaimsRequirement": {
 | 
			
		||||
        "UserType": "registered"
 | 
			
		||||
      },
 | 
			
		||||
      "RequestIdKey": "OcRequestId"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "DownstreamPathTemplate": "/posts",
 | 
			
		||||
      "DownstreamScheme": "https",
 | 
			
		||||
      "DownstreamHost": "jsonplaceholder.typicode.com",
 | 
			
		||||
      "DownstreamPort": 443,
 | 
			
		||||
      "UpstreamPathTemplate": "/posts",
 | 
			
		||||
      "UpstreamHttpMethod": "Get",
 | 
			
		||||
      "QoSOptions": {
 | 
			
		||||
        "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
        "DurationOfBreak": 10,
 | 
			
		||||
        "TimeoutValue": 5000
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "DownstreamPathTemplate": "/posts/{postId}",
 | 
			
		||||
      "DownstreamScheme": "http",
 | 
			
		||||
      "DownstreamHost": "jsonplaceholder.typicode.com",
 | 
			
		||||
      "DownstreamPort": 80,
 | 
			
		||||
      "UpstreamPathTemplate": "/posts/{postId}",
 | 
			
		||||
      "UpstreamHttpMethod": "Get",
 | 
			
		||||
      "QoSOptions": {
 | 
			
		||||
        "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
        "DurationOfBreak": 10,
 | 
			
		||||
        "TimeoutValue": 5000
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "DownstreamPathTemplate": "/posts/{postId}/comments",
 | 
			
		||||
      "DownstreamScheme": "http",
 | 
			
		||||
      "DownstreamHost": "jsonplaceholder.typicode.com",
 | 
			
		||||
      "DownstreamPort": 80,
 | 
			
		||||
      "UpstreamPathTemplate": "/posts/{postId}/comments",
 | 
			
		||||
      "UpstreamHttpMethod": "Get",
 | 
			
		||||
      "QoSOptions": {
 | 
			
		||||
        "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
        "DurationOfBreak": 10,
 | 
			
		||||
        "TimeoutValue": 5000
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "DownstreamPathTemplate": "/comments",
 | 
			
		||||
      "DownstreamScheme": "http",
 | 
			
		||||
      "DownstreamHost": "jsonplaceholder.typicode.com",
 | 
			
		||||
      "DownstreamPort": 80,
 | 
			
		||||
      "UpstreamPathTemplate": "/comments",
 | 
			
		||||
      "UpstreamHttpMethod": "Get",
 | 
			
		||||
      "QoSOptions": {
 | 
			
		||||
        "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
        "DurationOfBreak": 10,
 | 
			
		||||
        "TimeoutValue": 5000
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "DownstreamPathTemplate": "/posts",
 | 
			
		||||
      "DownstreamScheme": "http",
 | 
			
		||||
      "DownstreamHost": "jsonplaceholder.typicode.com",
 | 
			
		||||
      "DownstreamPort": 80,
 | 
			
		||||
      "UpstreamPathTemplate": "/posts",
 | 
			
		||||
      "UpstreamHttpMethod": "Post",
 | 
			
		||||
      "QoSOptions": {
 | 
			
		||||
        "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
        "DurationOfBreak": 10,
 | 
			
		||||
        "TimeoutValue": 5000
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "DownstreamPathTemplate": "/posts/{postId}",
 | 
			
		||||
      "DownstreamScheme": "http",
 | 
			
		||||
      "DownstreamHost": "jsonplaceholder.typicode.com",
 | 
			
		||||
      "DownstreamPort": 80,
 | 
			
		||||
      "UpstreamPathTemplate": "/posts/{postId}",
 | 
			
		||||
      "UpstreamHttpMethod": "Put",
 | 
			
		||||
      "QoSOptions": {
 | 
			
		||||
        "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
        "DurationOfBreak": 10,
 | 
			
		||||
        "TimeoutValue": 5000
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "DownstreamPathTemplate": "/posts/{postId}",
 | 
			
		||||
      "DownstreamScheme": "http",
 | 
			
		||||
      "DownstreamHost": "jsonplaceholder.typicode.com",
 | 
			
		||||
      "DownstreamPort": 80,
 | 
			
		||||
      "UpstreamPathTemplate": "/posts/{postId}",
 | 
			
		||||
      "UpstreamHttpMethod": "Patch",
 | 
			
		||||
      "QoSOptions": {
 | 
			
		||||
        "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
        "DurationOfBreak": 10,
 | 
			
		||||
        "TimeoutValue": 5000
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "DownstreamPathTemplate": "/posts/{postId}",
 | 
			
		||||
      "DownstreamScheme": "http",
 | 
			
		||||
      "DownstreamHost": "jsonplaceholder.typicode.com",
 | 
			
		||||
      "DownstreamPort": 80,
 | 
			
		||||
      "UpstreamPathTemplate": "/posts/{postId}",
 | 
			
		||||
      "UpstreamHttpMethod": "Delete",
 | 
			
		||||
      "QoSOptions": {
 | 
			
		||||
        "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
        "DurationOfBreak": 10,
 | 
			
		||||
        "TimeoutValue": 5000
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "DownstreamPathTemplate": "/api/products",
 | 
			
		||||
      "DownstreamScheme": "http",
 | 
			
		||||
      "DownstreamHost": "jsonplaceholder.typicode.com",
 | 
			
		||||
      "DownstreamPort": 80,
 | 
			
		||||
      "UpstreamPathTemplate": "/products",
 | 
			
		||||
      "UpstreamHttpMethod": "Get",
 | 
			
		||||
      "QoSOptions": {
 | 
			
		||||
        "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
        "DurationOfBreak": 10,
 | 
			
		||||
        "TimeoutValue": 5000
 | 
			
		||||
      },
 | 
			
		||||
      "FileCacheOptions": { "TtlSeconds": 15 }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "DownstreamPathTemplate": "/api/products/{productId}",
 | 
			
		||||
      "DownstreamScheme": "http",
 | 
			
		||||
      "DownstreamHost": "jsonplaceholder.typicode.com",
 | 
			
		||||
      "DownstreamPort": 80,
 | 
			
		||||
      "UpstreamPathTemplate": "/products/{productId}",
 | 
			
		||||
      "UpstreamHttpMethod": "Get",
 | 
			
		||||
      "FileCacheOptions": { "TtlSeconds": 15 }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "DownstreamPathTemplate": "/api/products",
 | 
			
		||||
      "DownstreamScheme": "http",
 | 
			
		||||
      "DownstreamHost": "products20161126090340.azurewebsites.net",
 | 
			
		||||
      "DownstreamPort": 80,
 | 
			
		||||
      "UpstreamPathTemplate": "/products",
 | 
			
		||||
      "UpstreamHttpMethod": "Post",
 | 
			
		||||
      "QoSOptions": {
 | 
			
		||||
        "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
        "DurationOfBreak": 10,
 | 
			
		||||
        "TimeoutValue": 5000
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "DownstreamPathTemplate": "/api/products/{productId}",
 | 
			
		||||
      "DownstreamScheme": "http",
 | 
			
		||||
      "DownstreamHost": "products20161126090340.azurewebsites.net",
 | 
			
		||||
      "DownstreamPort": 80,
 | 
			
		||||
      "UpstreamPathTemplate": "/products/{productId}",
 | 
			
		||||
      "UpstreamHttpMethod": "Put",
 | 
			
		||||
      "QoSOptions": {
 | 
			
		||||
        "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
        "DurationOfBreak": 10,
 | 
			
		||||
        "TimeoutValue": 5000
 | 
			
		||||
      },
 | 
			
		||||
      "FileCacheOptions": { "TtlSeconds": 15 }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "DownstreamPathTemplate": "/api/products/{productId}",
 | 
			
		||||
      "DownstreamScheme": "http",
 | 
			
		||||
      "DownstreamHost": "products20161126090340.azurewebsites.net",
 | 
			
		||||
      "DownstreamPort": 80,
 | 
			
		||||
      "UpstreamPathTemplate": "/products/{productId}",
 | 
			
		||||
      "UpstreamHttpMethod": "Delete",
 | 
			
		||||
      "QoSOptions": {
 | 
			
		||||
        "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
        "DurationOfBreak": 10,
 | 
			
		||||
        "TimeoutValue": 5000
 | 
			
		||||
      },
 | 
			
		||||
      "FileCacheOptions": { "TtlSeconds": 15 }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "DownstreamPathTemplate": "/api/customers",
 | 
			
		||||
      "DownstreamScheme": "http",
 | 
			
		||||
      "DownstreamHost": "customers20161126090811.azurewebsites.net",
 | 
			
		||||
      "DownstreamPort": 80,
 | 
			
		||||
      "UpstreamPathTemplate": "/customers",
 | 
			
		||||
      "UpstreamHttpMethod": "Get",
 | 
			
		||||
      "QoSOptions": {
 | 
			
		||||
        "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
        "DurationOfBreak": 10,
 | 
			
		||||
        "TimeoutValue": 5000
 | 
			
		||||
      },
 | 
			
		||||
      "FileCacheOptions": { "TtlSeconds": 15 }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "DownstreamPathTemplate": "/api/customers/{customerId}",
 | 
			
		||||
      "DownstreamScheme": "http",
 | 
			
		||||
      "DownstreamHost": "customers20161126090811.azurewebsites.net",
 | 
			
		||||
      "DownstreamPort": 80,
 | 
			
		||||
      "UpstreamPathTemplate": "/customers/{customerId}",
 | 
			
		||||
      "UpstreamHttpMethod": "Get",
 | 
			
		||||
      "QoSOptions": {
 | 
			
		||||
        "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
        "DurationOfBreak": 10,
 | 
			
		||||
        "TimeoutValue": 5000
 | 
			
		||||
      },
 | 
			
		||||
      "FileCacheOptions": { "TtlSeconds": 15 }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "DownstreamPathTemplate": "/api/customers",
 | 
			
		||||
      "DownstreamScheme": "http",
 | 
			
		||||
      "DownstreamHost": "customers20161126090811.azurewebsites.net",
 | 
			
		||||
      "DownstreamPort": 80,
 | 
			
		||||
      "UpstreamPathTemplate": "/customers",
 | 
			
		||||
      "UpstreamHttpMethod": "Post",
 | 
			
		||||
      "QoSOptions": {
 | 
			
		||||
        "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
        "DurationOfBreak": 10,
 | 
			
		||||
        "TimeoutValue": 5000
 | 
			
		||||
      },
 | 
			
		||||
      "FileCacheOptions": { "TtlSeconds": 15 }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "DownstreamPathTemplate": "/api/customers/{customerId}",
 | 
			
		||||
      "DownstreamScheme": "http",
 | 
			
		||||
      "DownstreamHost": "customers20161126090811.azurewebsites.net",
 | 
			
		||||
      "DownstreamPort": 80,
 | 
			
		||||
      "UpstreamPathTemplate": "/customers/{customerId}",
 | 
			
		||||
      "UpstreamHttpMethod": "Put",
 | 
			
		||||
      "QoSOptions": {
 | 
			
		||||
        "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
        "DurationOfBreak": 10,
 | 
			
		||||
        "TimeoutValue": 5000
 | 
			
		||||
      },
 | 
			
		||||
      "FileCacheOptions": { "TtlSeconds": 15 }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "DownstreamPathTemplate": "/api/customers/{customerId}",
 | 
			
		||||
      "DownstreamScheme": "http",
 | 
			
		||||
      "DownstreamHost": "customers20161126090811.azurewebsites.net",
 | 
			
		||||
      "DownstreamPort": 80,
 | 
			
		||||
      "UpstreamPathTemplate": "/customers/{customerId}",
 | 
			
		||||
      "UpstreamHttpMethod": "Delete",
 | 
			
		||||
      "QoSOptions": {
 | 
			
		||||
        "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
        "DurationOfBreak": 10,
 | 
			
		||||
        "TimeoutValue": 5000
 | 
			
		||||
      },
 | 
			
		||||
      "FileCacheOptions": { "TtlSeconds": 15 }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "DownstreamPathTemplate": "/posts",
 | 
			
		||||
      "DownstreamScheme": "http",
 | 
			
		||||
      "DownstreamHost": "jsonplaceholder.typicode.com",
 | 
			
		||||
      "DownstreamPort": 80,
 | 
			
		||||
      "UpstreamPathTemplate": "/posts/",
 | 
			
		||||
      "UpstreamHttpMethod": "Get",
 | 
			
		||||
      "QoSOptions": {
 | 
			
		||||
        "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
        "DurationOfBreak": 10,
 | 
			
		||||
        "TimeoutValue": 5000
 | 
			
		||||
      },
 | 
			
		||||
      "FileCacheOptions": { "TtlSeconds": 15 }
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
    "ReRoutes": [
 | 
			
		||||
        {
 | 
			
		||||
            "DownstreamPathTemplate": "/",
 | 
			
		||||
            "DownstreamScheme": "http",
 | 
			
		||||
            "DownstreamHost": "localhost",
 | 
			
		||||
            "DownstreamPort": 52876,
 | 
			
		||||
            "UpstreamPathTemplate": "/identityserverexample",
 | 
			
		||||
            "UpstreamHttpMethod": "Get",
 | 
			
		||||
            "QoSOptions": {
 | 
			
		||||
                "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
                "DurationOfBreak": 10,
 | 
			
		||||
                "TimeoutValue": 5000
 | 
			
		||||
            },
 | 
			
		||||
            "AuthenticationOptions": {
 | 
			
		||||
                "Provider": "IdentityServer",
 | 
			
		||||
                "ProviderRootUrl": "http://localhost:52888",
 | 
			
		||||
                "ScopeName": "api",
 | 
			
		||||
                "AdditionalScopes": [
 | 
			
		||||
                    "openid",
 | 
			
		||||
                    "offline_access"
 | 
			
		||||
                ],
 | 
			
		||||
                "ScopeSecret": "secret"
 | 
			
		||||
            },
 | 
			
		||||
            "AddHeadersToRequest": {
 | 
			
		||||
                "CustomerId": "Claims[CustomerId] > value",
 | 
			
		||||
                "LocationId": "Claims[LocationId] > value",
 | 
			
		||||
                "UserType": "Claims[sub] > value[0] > |",
 | 
			
		||||
                "UserId": "Claims[sub] > value[1] > |"
 | 
			
		||||
            },
 | 
			
		||||
            "AddClaimsToRequest": {
 | 
			
		||||
                "CustomerId": "Claims[CustomerId] > value",
 | 
			
		||||
                "LocationId": "Claims[LocationId] > value",
 | 
			
		||||
                "UserType": "Claims[sub] > value[0] > |",
 | 
			
		||||
                "UserId": "Claims[sub] > value[1] > |"
 | 
			
		||||
            },
 | 
			
		||||
            "AddQueriesToRequest": {
 | 
			
		||||
                "CustomerId": "Claims[CustomerId] > value",
 | 
			
		||||
                "LocationId": "Claims[LocationId] > value",
 | 
			
		||||
                "UserType": "Claims[sub] > value[0] > |",
 | 
			
		||||
                "UserId": "Claims[sub] > value[1] > |"
 | 
			
		||||
            },
 | 
			
		||||
            "RouteClaimsRequirement": {
 | 
			
		||||
                "UserType": "registered"
 | 
			
		||||
            },
 | 
			
		||||
            "RequestIdKey": "OcRequestId"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "DownstreamPathTemplate": "/posts",
 | 
			
		||||
            "DownstreamScheme": "https",
 | 
			
		||||
            "DownstreamHost": "jsonplaceholder.typicode.com",
 | 
			
		||||
            "DownstreamPort": 443,
 | 
			
		||||
            "UpstreamPathTemplate": "/posts",
 | 
			
		||||
            "UpstreamHttpMethod": "Get",
 | 
			
		||||
            "QoSOptions": {
 | 
			
		||||
                "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
                "DurationOfBreak": 10,
 | 
			
		||||
                "TimeoutValue": 5000
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "DownstreamPathTemplate": "/posts/{postId}",
 | 
			
		||||
            "DownstreamScheme": "http",
 | 
			
		||||
            "DownstreamHost": "jsonplaceholder.typicode.com",
 | 
			
		||||
            "DownstreamPort": 80,
 | 
			
		||||
            "UpstreamPathTemplate": "/posts/{postId}",
 | 
			
		||||
            "UpstreamHttpMethod": "Get",
 | 
			
		||||
            "QoSOptions": {
 | 
			
		||||
                "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
                "DurationOfBreak": 10,
 | 
			
		||||
                "TimeoutValue": 5000
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "DownstreamPathTemplate": "/posts/{postId}/comments",
 | 
			
		||||
            "DownstreamScheme": "http",
 | 
			
		||||
            "DownstreamHost": "jsonplaceholder.typicode.com",
 | 
			
		||||
            "DownstreamPort": 80,
 | 
			
		||||
            "UpstreamPathTemplate": "/posts/{postId}/comments",
 | 
			
		||||
            "UpstreamHttpMethod": "Get",
 | 
			
		||||
            "QoSOptions": {
 | 
			
		||||
                "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
                "DurationOfBreak": 10,
 | 
			
		||||
                "TimeoutValue": 5000
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "DownstreamPathTemplate": "/comments",
 | 
			
		||||
            "DownstreamScheme": "http",
 | 
			
		||||
            "DownstreamHost": "jsonplaceholder.typicode.com",
 | 
			
		||||
            "DownstreamPort": 80,
 | 
			
		||||
            "UpstreamPathTemplate": "/comments",
 | 
			
		||||
            "UpstreamHttpMethod": "Get",
 | 
			
		||||
            "QoSOptions": {
 | 
			
		||||
                "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
                "DurationOfBreak": 10,
 | 
			
		||||
                "TimeoutValue": 5000
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "DownstreamPathTemplate": "/posts",
 | 
			
		||||
            "DownstreamScheme": "http",
 | 
			
		||||
            "DownstreamHost": "jsonplaceholder.typicode.com",
 | 
			
		||||
            "DownstreamPort": 80,
 | 
			
		||||
            "UpstreamPathTemplate": "/posts",
 | 
			
		||||
            "UpstreamHttpMethod": "Post",
 | 
			
		||||
            "QoSOptions": {
 | 
			
		||||
                "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
                "DurationOfBreak": 10,
 | 
			
		||||
                "TimeoutValue": 5000
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "DownstreamPathTemplate": "/posts/{postId}",
 | 
			
		||||
            "DownstreamScheme": "http",
 | 
			
		||||
            "DownstreamHost": "jsonplaceholder.typicode.com",
 | 
			
		||||
            "DownstreamPort": 80,
 | 
			
		||||
            "UpstreamPathTemplate": "/posts/{postId}",
 | 
			
		||||
            "UpstreamHttpMethod": "Put",
 | 
			
		||||
            "QoSOptions": {
 | 
			
		||||
                "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
                "DurationOfBreak": 10,
 | 
			
		||||
                "TimeoutValue": 5000
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "DownstreamPathTemplate": "/posts/{postId}",
 | 
			
		||||
            "DownstreamScheme": "http",
 | 
			
		||||
            "DownstreamHost": "jsonplaceholder.typicode.com",
 | 
			
		||||
            "DownstreamPort": 80,
 | 
			
		||||
            "UpstreamPathTemplate": "/posts/{postId}",
 | 
			
		||||
            "UpstreamHttpMethod": "Patch",
 | 
			
		||||
            "QoSOptions": {
 | 
			
		||||
                "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
                "DurationOfBreak": 10,
 | 
			
		||||
                "TimeoutValue": 5000
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "DownstreamPathTemplate": "/posts/{postId}",
 | 
			
		||||
            "DownstreamScheme": "http",
 | 
			
		||||
            "DownstreamHost": "jsonplaceholder.typicode.com",
 | 
			
		||||
            "DownstreamPort": 80,
 | 
			
		||||
            "UpstreamPathTemplate": "/posts/{postId}",
 | 
			
		||||
            "UpstreamHttpMethod": "Delete",
 | 
			
		||||
            "QoSOptions": {
 | 
			
		||||
                "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
                "DurationOfBreak": 10,
 | 
			
		||||
                "TimeoutValue": 5000
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "DownstreamPathTemplate": "/api/products",
 | 
			
		||||
            "DownstreamScheme": "http",
 | 
			
		||||
            "DownstreamHost": "jsonplaceholder.typicode.com",
 | 
			
		||||
            "DownstreamPort": 80,
 | 
			
		||||
            "UpstreamPathTemplate": "/products",
 | 
			
		||||
            "UpstreamHttpMethod": "Get",
 | 
			
		||||
            "QoSOptions": {
 | 
			
		||||
                "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
                "DurationOfBreak": 10,
 | 
			
		||||
                "TimeoutValue": 5000
 | 
			
		||||
            },
 | 
			
		||||
            "FileCacheOptions": { "TtlSeconds": 15 }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "DownstreamPathTemplate": "/api/products/{productId}",
 | 
			
		||||
            "DownstreamScheme": "http",
 | 
			
		||||
            "DownstreamHost": "jsonplaceholder.typicode.com",
 | 
			
		||||
            "DownstreamPort": 80,
 | 
			
		||||
            "UpstreamPathTemplate": "/products/{productId}",
 | 
			
		||||
            "UpstreamHttpMethod": "Get",
 | 
			
		||||
            "FileCacheOptions": { "TtlSeconds": 15 }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "DownstreamPathTemplate": "/api/products",
 | 
			
		||||
            "DownstreamScheme": "http",
 | 
			
		||||
            "DownstreamHost": "products20161126090340.azurewebsites.net",
 | 
			
		||||
            "DownstreamPort": 80,
 | 
			
		||||
            "UpstreamPathTemplate": "/products",
 | 
			
		||||
            "UpstreamHttpMethod": "Post",
 | 
			
		||||
            "QoSOptions": {
 | 
			
		||||
                "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
                "DurationOfBreak": 10,
 | 
			
		||||
                "TimeoutValue": 5000
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "DownstreamPathTemplate": "/api/products/{productId}",
 | 
			
		||||
            "DownstreamScheme": "http",
 | 
			
		||||
            "DownstreamHost": "products20161126090340.azurewebsites.net",
 | 
			
		||||
            "DownstreamPort": 80,
 | 
			
		||||
            "UpstreamPathTemplate": "/products/{productId}",
 | 
			
		||||
            "UpstreamHttpMethod": "Put",
 | 
			
		||||
            "QoSOptions": {
 | 
			
		||||
                "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
                "DurationOfBreak": 10,
 | 
			
		||||
                "TimeoutValue": 5000
 | 
			
		||||
            },
 | 
			
		||||
            "FileCacheOptions": { "TtlSeconds": 15 }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "DownstreamPathTemplate": "/api/products/{productId}",
 | 
			
		||||
            "DownstreamScheme": "http",
 | 
			
		||||
            "DownstreamHost": "products20161126090340.azurewebsites.net",
 | 
			
		||||
            "DownstreamPort": 80,
 | 
			
		||||
            "UpstreamPathTemplate": "/products/{productId}",
 | 
			
		||||
            "UpstreamHttpMethod": "Delete",
 | 
			
		||||
            "QoSOptions": {
 | 
			
		||||
                "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
                "DurationOfBreak": 10,
 | 
			
		||||
                "TimeoutValue": 5000
 | 
			
		||||
            },
 | 
			
		||||
            "FileCacheOptions": { "TtlSeconds": 15 }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "DownstreamPathTemplate": "/api/customers",
 | 
			
		||||
            "DownstreamScheme": "http",
 | 
			
		||||
            "DownstreamHost": "customers20161126090811.azurewebsites.net",
 | 
			
		||||
            "DownstreamPort": 80,
 | 
			
		||||
            "UpstreamPathTemplate": "/customers",
 | 
			
		||||
            "UpstreamHttpMethod": "Get",
 | 
			
		||||
            "QoSOptions": {
 | 
			
		||||
                "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
                "DurationOfBreak": 10,
 | 
			
		||||
                "TimeoutValue": 5000
 | 
			
		||||
            },
 | 
			
		||||
            "FileCacheOptions": { "TtlSeconds": 15 }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "DownstreamPathTemplate": "/api/customers/{customerId}",
 | 
			
		||||
            "DownstreamScheme": "http",
 | 
			
		||||
            "DownstreamHost": "customers20161126090811.azurewebsites.net",
 | 
			
		||||
            "DownstreamPort": 80,
 | 
			
		||||
            "UpstreamPathTemplate": "/customers/{customerId}",
 | 
			
		||||
            "UpstreamHttpMethod": "Get",
 | 
			
		||||
            "QoSOptions": {
 | 
			
		||||
                "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
                "DurationOfBreak": 10,
 | 
			
		||||
                "TimeoutValue": 5000
 | 
			
		||||
            },
 | 
			
		||||
            "FileCacheOptions": { "TtlSeconds": 15 }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "DownstreamPathTemplate": "/api/customers",
 | 
			
		||||
            "DownstreamScheme": "http",
 | 
			
		||||
            "DownstreamHost": "customers20161126090811.azurewebsites.net",
 | 
			
		||||
            "DownstreamPort": 80,
 | 
			
		||||
            "UpstreamPathTemplate": "/customers",
 | 
			
		||||
            "UpstreamHttpMethod": "Post",
 | 
			
		||||
            "QoSOptions": {
 | 
			
		||||
                "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
                "DurationOfBreak": 10,
 | 
			
		||||
                "TimeoutValue": 5000
 | 
			
		||||
            },
 | 
			
		||||
            "FileCacheOptions": { "TtlSeconds": 15 }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "DownstreamPathTemplate": "/api/customers/{customerId}",
 | 
			
		||||
            "DownstreamScheme": "http",
 | 
			
		||||
            "DownstreamHost": "customers20161126090811.azurewebsites.net",
 | 
			
		||||
            "DownstreamPort": 80,
 | 
			
		||||
            "UpstreamPathTemplate": "/customers/{customerId}",
 | 
			
		||||
            "UpstreamHttpMethod": "Put",
 | 
			
		||||
            "QoSOptions": {
 | 
			
		||||
                "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
                "DurationOfBreak": 10,
 | 
			
		||||
                "TimeoutValue": 5000
 | 
			
		||||
            },
 | 
			
		||||
            "FileCacheOptions": { "TtlSeconds": 15 }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "DownstreamPathTemplate": "/api/customers/{customerId}",
 | 
			
		||||
            "DownstreamScheme": "http",
 | 
			
		||||
            "DownstreamHost": "customers20161126090811.azurewebsites.net",
 | 
			
		||||
            "DownstreamPort": 80,
 | 
			
		||||
            "UpstreamPathTemplate": "/customers/{customerId}",
 | 
			
		||||
            "UpstreamHttpMethod": "Delete",
 | 
			
		||||
            "QoSOptions": {
 | 
			
		||||
                "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
                "DurationOfBreak": 10,
 | 
			
		||||
                "TimeoutValue": 5000
 | 
			
		||||
            },
 | 
			
		||||
            "FileCacheOptions": { "TtlSeconds": 15 }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "DownstreamPathTemplate": "/posts",
 | 
			
		||||
            "DownstreamScheme": "http",
 | 
			
		||||
            "DownstreamHost": "jsonplaceholder.typicode.com",
 | 
			
		||||
            "DownstreamPort": 80,
 | 
			
		||||
            "UpstreamPathTemplate": "/posts/",
 | 
			
		||||
            "UpstreamHttpMethod": "Get",
 | 
			
		||||
            "QoSOptions": {
 | 
			
		||||
                "ExceptionsAllowedBeforeBreaking": 3,
 | 
			
		||||
                "DurationOfBreak": 10,
 | 
			
		||||
                "TimeoutValue": 5000
 | 
			
		||||
            },
 | 
			
		||||
            "FileCacheOptions": { "TtlSeconds": 15 }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "DownstreamPathTemplate": "/",
 | 
			
		||||
            "DownstreamScheme": "http",
 | 
			
		||||
            "DownstreamHost": "www.bbc.co.uk",
 | 
			
		||||
            "DownstreamPort": 80,
 | 
			
		||||
            "UpstreamPathTemplate": "/bbc/",
 | 
			
		||||
            "UpstreamHttpMethod": "Get"
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
  "GlobalConfiguration": {
 | 
			
		||||
    "RequestIdKey": "OcRequestId",
 | 
			
		||||
 
 | 
			
		||||
@@ -79,7 +79,7 @@ namespace Ocelot.UnitTests.Request
 | 
			
		||||
            this.Given(x => x.GivenTheDownStreamUrlIs("any old string"))
 | 
			
		||||
                .And(x => x.GivenTheQosProviderHouseReturns(new OkResponse<IQoSProvider>(new NoQoSProvider())))
 | 
			
		||||
                .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute))
 | 
			
		||||
                .And(x => x.GivenTheRequestBuilderReturns(new Ocelot.Request.Request(new HttpRequestMessage(), new CookieContainer(), true, new NoQoSProvider())))
 | 
			
		||||
                .And(x => x.GivenTheRequestBuilderReturns(new Ocelot.Request.Request(new HttpRequestMessage(), true, new NoQoSProvider())))
 | 
			
		||||
                .When(x => x.WhenICallTheMiddleware())
 | 
			
		||||
                .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly())
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
@@ -104,8 +104,8 @@ namespace Ocelot.UnitTests.Request
 | 
			
		||||
        {
 | 
			
		||||
            _request = new OkResponse<Ocelot.Request.Request>(request);
 | 
			
		||||
            _requestBuilder
 | 
			
		||||
                .Setup(x => x.Build(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Stream>(), It.IsAny<IHeaderDictionary>(),
 | 
			
		||||
                It.IsAny<IRequestCookieCollection>(), It.IsAny<QueryString>(), It.IsAny<string>(), It.IsAny<Ocelot.RequestId.RequestId>(),It.IsAny<bool>(), It.IsAny<IQoSProvider>()))
 | 
			
		||||
                .Setup(x => x.Build(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Stream>(), It.IsAny<IHeaderDictionary>(), 
 | 
			
		||||
                It.IsAny<QueryString>(), It.IsAny<string>(), It.IsAny<Ocelot.RequestId.RequestId>(),It.IsAny<bool>(), It.IsAny<IQoSProvider>()))
 | 
			
		||||
                .ReturnsAsync(_request);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -195,23 +195,6 @@ namespace Ocelot.UnitTests.Request
 | 
			
		||||
            _qoSProvider = qoSProvider;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void should_use_cookies()
 | 
			
		||||
        {
 | 
			
		||||
            this.Given(x => x.GivenIHaveHttpMethod("GET"))
 | 
			
		||||
               .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk"))
 | 
			
		||||
               .And(x => x.GivenTheCookiesAre(new RequestCookieCollection(new Dictionary<string, string>
 | 
			
		||||
               {
 | 
			
		||||
                   { "TheCookie","Monster" }
 | 
			
		||||
               })))
 | 
			
		||||
               .When(x => x.WhenICreateARequest())
 | 
			
		||||
               .And(x => x.ThenTheCorrectCookiesAreUsed(new RequestCookieCollection(new Dictionary<string, string>
 | 
			
		||||
               {
 | 
			
		||||
                   { "TheCookie","Monster" }
 | 
			
		||||
               })))
 | 
			
		||||
               .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void should_user_query_string()
 | 
			
		||||
        {
 | 
			
		||||
@@ -240,14 +223,14 @@ namespace Ocelot.UnitTests.Request
 | 
			
		||||
 | 
			
		||||
        private void ThenTheCorrectCookiesAreUsed(IRequestCookieCollection expected)
 | 
			
		||||
        {
 | 
			
		||||
            var resultCookies = _result.Data.CookieContainer.GetCookies(new Uri(_downstreamUrl + _query));
 | 
			
		||||
           /* var resultCookies = _result.Data.CookieContainer.GetCookies(new Uri(_downstreamUrl + _query));
 | 
			
		||||
            var resultDictionary = resultCookies.Cast<Cookie>().ToDictionary(cook => cook.Name, cook => cook.Value);
 | 
			
		||||
 | 
			
		||||
            foreach (var expectedCookie in expected)
 | 
			
		||||
            {
 | 
			
		||||
                var resultCookie = resultDictionary[expectedCookie.Key];
 | 
			
		||||
                resultCookie.ShouldBe(expectedCookie.Value);
 | 
			
		||||
            }
 | 
			
		||||
            }*/
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void GivenTheCookiesAre(IRequestCookieCollection cookies)
 | 
			
		||||
@@ -305,7 +288,7 @@ namespace Ocelot.UnitTests.Request
 | 
			
		||||
        private void WhenICreateARequest()
 | 
			
		||||
        {
 | 
			
		||||
            _result = _requestCreator.Build(_httpMethod, _downstreamUrl, _content?.ReadAsStreamAsync().Result, _headers,
 | 
			
		||||
                _cookies, _query, _contentType, _requestId,_isQos,_qoSProvider).Result;
 | 
			
		||||
                _query, _contentType, _requestId,_isQos,_qoSProvider).Result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -62,7 +62,7 @@ namespace Ocelot.UnitTests.Requester
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void should_call_scoped_data_repository_correctly()
 | 
			
		||||
        {
 | 
			
		||||
            this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage(),new CookieContainer(),true, new NoQoSProvider())))
 | 
			
		||||
            this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage(),true, new NoQoSProvider())))
 | 
			
		||||
                .And(x => x.GivenTheRequesterReturns(new HttpResponseMessage()))
 | 
			
		||||
                .And(x => x.GivenTheScopedRepoReturns())
 | 
			
		||||
                .When(x => x.WhenICallTheMiddleware())
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user