From 10db534008060343665dd75f391c0fb2691c20ca Mon Sep 17 00:00:00 2001 From: geffzhang Date: Sat, 4 Mar 2017 18:18:00 +0800 Subject: [PATCH 1/8] Refactor HttpClientHttpRequester Cache HttpClient --- .../ServiceCollectionExtensions.cs | 1 + src/Ocelot/Requester/HttpClientBuilder.cs | 104 ++++++++++++++---- .../Requester/HttpClientHttpRequester.cs | 70 +++++++----- src/Ocelot/Requester/IHttpClient.cs | 13 +++ src/Ocelot/Requester/IHttpClientBuilder.cs | 42 +++++++ .../IHttpClientMessageCacheHandler.cs | 16 +++ .../MemoryHttpClientMessageCacheHandler.cs | 46 ++++++++ .../PollyCircuitBreakingDelegatingHandler.cs | 4 +- 8 files changed, 243 insertions(+), 53 deletions(-) create mode 100644 src/Ocelot/Requester/IHttpClient.cs create mode 100644 src/Ocelot/Requester/IHttpClientBuilder.cs create mode 100644 src/Ocelot/Requester/IHttpClientMessageCacheHandler.cs create mode 100644 src/Ocelot/Requester/MemoryHttpClientMessageCacheHandler.cs diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index c076f367..04ca1037 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -135,6 +135,7 @@ namespace Ocelot.DependencyInjection services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); // 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 diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index da37ee02..8cad9d49 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -1,46 +1,110 @@ 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> _handlers = new Dictionary>(); + private TimeSpan? _timeout; + private readonly List _handlers = new List(); + private Dictionary _defaultHeaders; + private CookieContainer _cookieContainer; + private IQoSProvider _qoSProvider; - public HttpClientBuilder WithQoS(IQoSProvider qoSProvider, IOcelotLogger logger, HttpMessageHandler innerHandler) + public IHttpClientBuilder WithCookieContainer(CookieContainer cookieContainer) { - _handlers.Add(5000, () => new PollyCircuitBreakingDelegatingHandler(qoSProvider, logger, innerHandler)); + _cookieContainer = cookieContainer; return this; } - internal HttpClient Build(HttpMessageHandler innerHandler) + public IHttpClientBuilder WithTimeout(TimeSpan timeout) { - return _handlers.Any() ? - new HttpClient(CreateHttpMessageHandler()) : - new HttpClient(innerHandler); + _timeout = timeout; + return this; } - private HttpMessageHandler CreateHttpMessageHandler() + public IHttpClientBuilder WithHandler(DelegatingHandler handler) { - HttpMessageHandler httpMessageHandler = new HttpClientHandler(); + _handlers.Add(handler); + return this; + } - _handlers - .OrderByDescending(handler => handler.Key) - .Select(handler => handler.Value) - .Reverse() - .ToList() - .ForEach(handler => + public IHttpClientBuilder WithDefaultRequestHeaders(Dictionary headers) + { + _defaultHeaders = headers; + return this; + } + + + public IHttpClient Create() + { + HttpClientHandler httpclientHandler = null; + if (_cookieContainer != null) { - var delegatingHandler = handler(); - delegatingHandler.InnerHandler = httpMessageHandler; - httpMessageHandler = delegatingHandler; - }); + httpclientHandler = new HttpClientHandler() { CookieContainer = _cookieContainer }; + } + else + { + httpclientHandler = new HttpClientHandler(); + } + if (httpclientHandler.SupportsAutomaticDecompression) + { + httpclientHandler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; + } + + var client = new HttpClient(CreateHttpMessageHandler(httpclientHandler)); + + if (_timeout.HasValue) + { + client.Timeout = _timeout.Value; + } + + if (_defaultHeaders == null) + { + return new HttpClientWrapper(client); + } + + foreach (var header in _defaultHeaders) + { + client.DefaultRequestHeaders.Add(header.Key, header.Value); + } + + return new HttpClientWrapper(client); + } + + private HttpMessageHandler CreateHttpMessageHandler(HttpMessageHandler httpMessageHandler) + { + foreach (var handler in _handlers) + { + handler.InnerHandler = httpMessageHandler; + httpMessageHandler = handler; + } return httpMessageHandler; } } + + /// + /// This class was made to make unit testing easier when HttpClient is used. + /// + internal class HttpClientWrapper : IHttpClient + { + public HttpClient Client { get; } + + public HttpClientWrapper(HttpClient client) + { + Client = client; + } + + public Task SendAsync(HttpRequestMessage request) + { + return Client.SendAsync(request); + } + } } diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index 5a2c86c7..f0566fed 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Net.Http; using System.Threading.Tasks; using Ocelot.Logging; @@ -10,47 +11,56 @@ namespace Ocelot.Requester { public class HttpClientHttpRequester : IHttpRequester { + private IHttpClientMessageCacheHandler _cacheHandlers; private readonly IOcelotLogger _logger; - public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory) + public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory, IHttpClientMessageCacheHandler cacheHandlers) { _logger = loggerFactory.CreateLogger(); + _cacheHandlers = cacheHandlers; } public async Task> GetResponse(Request.Request request) { - var builder = new HttpClientBuilder(); + var builder = new HttpClientBuilder(); - using (var handler = new HttpClientHandler { CookieContainer = request.CookieContainer }) + builder.WithCookieContainer(request.CookieContainer); + + string baseUrl = $"{request.HttpRequestMessage.RequestUri.Scheme}://{request.HttpRequestMessage.RequestUri.Authority}"; + + if (request.IsQos) { - 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(response); - } - catch (TimeoutRejectedException exception) - { - return - new ErrorResponse(new RequestTimedOutError(exception)); - } - catch (BrokenCircuitException exception) - { - return - new ErrorResponse(new RequestTimedOutError(exception)); - } - catch (Exception exception) - { - return new ErrorResponse(new UnableToCompleteRequestError(exception)); - } - } + builder.WithHandler(new PollyCircuitBreakingDelegatingHandler(request.QosProvider, _logger)); + baseUrl = $"{baseUrl}{request.QosProvider.CircuitBreaker.CircuitBreakerPolicy.PolicyKey}"; } + + IHttpClient httpClient = _cacheHandlers.Get(baseUrl); + if (httpClient == null) + { + httpClient = builder.Create(); + _cacheHandlers.Set(baseUrl, httpClient, TimeSpan.FromMinutes(30)); + } + + try + { + var response = await httpClient.SendAsync(request.HttpRequestMessage); + return new OkResponse(response); + } + catch (TimeoutRejectedException exception) + { + return + new ErrorResponse(new RequestTimedOutError(exception)); + } + catch (BrokenCircuitException exception) + { + return + new ErrorResponse(new RequestTimedOutError(exception)); + } + catch (Exception exception) + { + return new ErrorResponse(new UnableToCompleteRequestError(exception)); + } + } } } \ No newline at end of file diff --git a/src/Ocelot/Requester/IHttpClient.cs b/src/Ocelot/Requester/IHttpClient.cs new file mode 100644 index 00000000..8a169a36 --- /dev/null +++ b/src/Ocelot/Requester/IHttpClient.cs @@ -0,0 +1,13 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Ocelot.Requester +{ + public interface IHttpClient + { + HttpClient Client { get; } + + Task SendAsync(HttpRequestMessage request); + } +} diff --git a/src/Ocelot/Requester/IHttpClientBuilder.cs b/src/Ocelot/Requester/IHttpClientBuilder.cs new file mode 100644 index 00000000..85fd5f69 --- /dev/null +++ b/src/Ocelot/Requester/IHttpClientBuilder.cs @@ -0,0 +1,42 @@ +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 + { + /// + /// Sets the cookie container used to store server cookies by the handler + /// + /// + /// + IHttpClientBuilder WithCookieContainer(CookieContainer cookieContainer); + + /// + /// Sets the number of milliseconds to wait before the request times out. + /// + IHttpClientBuilder WithTimeout(TimeSpan timeout); + + /// + /// Sets a DelegatingHandler. + /// Can be reused to set several different Handlers in a pipeline. + /// + IHttpClientBuilder WithHandler(DelegatingHandler handler); + + /// + /// Sets Default HttpRequestHeaders + /// + IHttpClientBuilder WithDefaultRequestHeaders(Dictionary headers); + + /// + /// Creates the + /// + IHttpClient Create(); + } +} diff --git a/src/Ocelot/Requester/IHttpClientMessageCacheHandler.cs b/src/Ocelot/Requester/IHttpClientMessageCacheHandler.cs new file mode 100644 index 00000000..a2a93692 --- /dev/null +++ b/src/Ocelot/Requester/IHttpClientMessageCacheHandler.cs @@ -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 IHttpClientMessageCacheHandler + { + bool Exists(string id); + IHttpClient Get(string id); + void Remove(string id); + void Set(string id, IHttpClient handler, TimeSpan expirationTime); + } +} diff --git a/src/Ocelot/Requester/MemoryHttpClientMessageCacheHandler.cs b/src/Ocelot/Requester/MemoryHttpClientMessageCacheHandler.cs new file mode 100644 index 00000000..bb948b8a --- /dev/null +++ b/src/Ocelot/Requester/MemoryHttpClientMessageCacheHandler.cs @@ -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 MemoryHttpClientMessageCacheHandler : IHttpClientMessageCacheHandler + { + private readonly IMemoryCache _memoryCache; + + public MemoryHttpClientMessageCacheHandler(IMemoryCache memoryCache) + { + _memoryCache = memoryCache; + } + + public void Set(string id, IHttpClient counter, TimeSpan expirationTime) + { + _memoryCache.Set(id, counter, 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); + } + } +} diff --git a/src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs b/src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs index a50ec91b..a09ed233 100644 --- a/src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs +++ b/src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs @@ -16,9 +16,7 @@ namespace Ocelot.Requester public PollyCircuitBreakingDelegatingHandler( IQoSProvider qoSProvider, - IOcelotLogger logger, - HttpMessageHandler innerHandler) - : base(innerHandler) + IOcelotLogger logger) { _qoSProvider = qoSProvider; _logger = logger; From c787202374322dcbd09473a4467664a3d7767def Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 5 Mar 2017 18:05:20 +0000 Subject: [PATCH 2/8] I realised we can get rid of the cookie container as cookies are just sent as a header called cookie... --- .../Request/Builder/HttpRequestCreator.cs | 2 - src/Ocelot/Request/Builder/IRequestCreator.cs | 1 - src/Ocelot/Request/Builder/RequestBuilder.cs | 26 +- .../HttpRequestBuilderMiddleware.cs | 1 - src/Ocelot/Request/Request.cs | 8 +- src/Ocelot/Requester/HttpClientBuilder.cs | 8 +- .../Requester/HttpClientHttpRequester.cs | 49 +- .../PollyCircuitBreakingDelegatingHandler.cs | 4 +- test/Ocelot.ManualTest/configuration.json | 606 +++++++++--------- .../HttpRequestBuilderMiddlewareTests.cs | 6 +- .../Request/RequestBuilderTests.cs | 23 +- .../Requester/HttpRequesterMiddlewareTests.cs | 2 +- 12 files changed, 344 insertions(+), 392 deletions(-) diff --git a/src/Ocelot/Request/Builder/HttpRequestCreator.cs b/src/Ocelot/Request/Builder/HttpRequestCreator.cs index 3264db59..de030f83 100644 --- a/src/Ocelot/Request/Builder/HttpRequestCreator.cs +++ b/src/Ocelot/Request/Builder/HttpRequestCreator.cs @@ -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(); diff --git a/src/Ocelot/Request/Builder/IRequestCreator.cs b/src/Ocelot/Request/Builder/IRequestCreator.cs index 7db999b1..379f0aac 100644 --- a/src/Ocelot/Request/Builder/IRequestCreator.cs +++ b/src/Ocelot/Request/Builder/IRequestCreator.cs @@ -12,7 +12,6 @@ namespace Ocelot.Request.Builder string downstreamUrl, Stream content, IHeaderDictionary headers, - IRequestCookieCollection cookies, QueryString queryString, string contentType, RequestId.RequestId requestId, diff --git a/src/Ocelot/Request/Builder/RequestBuilder.cs b/src/Ocelot/Request/Builder/RequestBuilder.cs index ea6511ce..e47eea1f 100644 --- a/src/Ocelot/Request/Builder/RequestBuilder.cs +++ b/src/Ocelot/Request/Builder/RequestBuilder.cs @@ -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); diff --git a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs index 991e84da..0701e089 100644 --- a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs +++ b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs @@ -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), diff --git a/src/Ocelot/Request/Request.cs b/src/Ocelot/Request/Request.cs index 680ab757..3f1c654c 100644 --- a/src/Ocelot/Request/Request.cs +++ b/src/Ocelot/Request/Request.cs @@ -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; } } diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index da37ee02..71462644 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -11,17 +11,17 @@ namespace Ocelot.Requester { private readonly Dictionary> _handlers = new Dictionary>(); - public HttpClientBuilder WithQoS(IQoSProvider qoSProvider, IOcelotLogger logger, HttpMessageHandler innerHandler) + public HttpClientBuilder WithQoS(IQoSProvider qoSProvider, IOcelotLogger logger) { - _handlers.Add(5000, () => new PollyCircuitBreakingDelegatingHandler(qoSProvider, logger, innerHandler)); + _handlers.Add(5000, () => new PollyCircuitBreakingDelegatingHandler(qoSProvider, logger)); return this; } - internal HttpClient Build(HttpMessageHandler innerHandler) + internal HttpClient Build() { return _handlers.Any() ? new HttpClient(CreateHttpMessageHandler()) : - new HttpClient(innerHandler); + new HttpClient(); } private HttpMessageHandler CreateHttpMessageHandler() diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index 5a2c86c7..08839ccc 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -19,36 +19,33 @@ namespace Ocelot.Requester public async Task> GetResponse(Request.Request request) { - var builder = new HttpClientBuilder(); + var builder = new HttpClientBuilder(); - using (var handler = new HttpClientHandler { CookieContainer = request.CookieContainer }) + if (request.IsQos) { - if (request.IsQos) - { - builder.WithQoS(request.QosProvider, _logger, handler); - } + builder.WithQoS(request.QosProvider, _logger); + } - using (var httpClient = builder.Build(handler)) + using (var httpClient = builder.Build()) + { + try { - try - { - var response = await httpClient.SendAsync(request.HttpRequestMessage); - return new OkResponse(response); - } - catch (TimeoutRejectedException exception) - { - return - new ErrorResponse(new RequestTimedOutError(exception)); - } - catch (BrokenCircuitException exception) - { - return - new ErrorResponse(new RequestTimedOutError(exception)); - } - catch (Exception exception) - { - return new ErrorResponse(new UnableToCompleteRequestError(exception)); - } + var response = await httpClient.SendAsync(request.HttpRequestMessage); + return new OkResponse(response); + } + catch (TimeoutRejectedException exception) + { + return + new ErrorResponse(new RequestTimedOutError(exception)); + } + catch (BrokenCircuitException exception) + { + return + new ErrorResponse(new RequestTimedOutError(exception)); + } + catch (Exception exception) + { + return new ErrorResponse(new UnableToCompleteRequestError(exception)); } } } diff --git a/src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs b/src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs index a50ec91b..a09ed233 100644 --- a/src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs +++ b/src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs @@ -16,9 +16,7 @@ namespace Ocelot.Requester public PollyCircuitBreakingDelegatingHandler( IQoSProvider qoSProvider, - IOcelotLogger logger, - HttpMessageHandler innerHandler) - : base(innerHandler) + IOcelotLogger logger) { _qoSProvider = qoSProvider; _logger = logger; diff --git a/test/Ocelot.ManualTest/configuration.json b/test/Ocelot.ManualTest/configuration.json index 980614bd..b28080d2 100644 --- a/test/Ocelot.ManualTest/configuration.json +++ b/test/Ocelot.ManualTest/configuration.json @@ -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", diff --git a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs index b674b015..002e4e02 100644 --- a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs @@ -79,7 +79,7 @@ namespace Ocelot.UnitTests.Request this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) .And(x => x.GivenTheQosProviderHouseReturns(new OkResponse(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(request); _requestBuilder - .Setup(x => x.Build(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(),It.IsAny(), It.IsAny())) + .Setup(x => x.Build(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(),It.IsAny(), It.IsAny())) .ReturnsAsync(_request); } diff --git a/test/Ocelot.UnitTests/Request/RequestBuilderTests.cs b/test/Ocelot.UnitTests/Request/RequestBuilderTests.cs index 9d1c2ed5..667c80b0 100644 --- a/test/Ocelot.UnitTests/Request/RequestBuilderTests.cs +++ b/test/Ocelot.UnitTests/Request/RequestBuilderTests.cs @@ -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 - { - { "TheCookie","Monster" } - }))) - .When(x => x.WhenICreateARequest()) - .And(x => x.ThenTheCorrectCookiesAreUsed(new RequestCookieCollection(new Dictionary - { - { "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().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; } diff --git a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs index 37815a64..b95367be 100644 --- a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs @@ -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()) From fac2346161f76512883e1fbfb35e4525ea65d82c Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 6 Mar 2017 07:33:55 +0000 Subject: [PATCH 3/8] added thread safe test --- configuration.json | 2 +- .../ThreadSafeHeadersTests.cs | 242 ++++++++++++++++++ .../configuration.json | 2 +- 3 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs diff --git a/configuration.json b/configuration.json index 3f39532c..6c4e9ae7 100755 --- a/configuration.json +++ b/configuration.json @@ -1 +1 @@ -{"ReRoutes":[],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":"/administration"}} \ No newline at end of file +{"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}}} \ No newline at end of file diff --git a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs new file mode 100644 index 00000000..c8792f5d --- /dev/null +++ b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs @@ -0,0 +1,242 @@ +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 ConcurrentBag _results; + + public ThreadSafeHeadersTests() + { + _results = new ConcurrentBag(); + _random = new Random(); + _httpClient = new HttpClient(); + _ocelotBaseUrl = "http://localhost:5000"; + _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 + { + 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("/", 50)) + .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 WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration) + { + var json = JsonConvert.SerializeObject(updatedConfiguration); + var content = new StringContent(json); + content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + _response = _httpClient.PostAsync(url, content).Result; + } + + private void ThenTheResponseShouldBe(FileConfiguration expected) + { + var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); + + response.GlobalConfiguration.AdministrationPath.ShouldBe(expected.GlobalConfiguration.AdministrationPath); + response.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey); + response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host); + response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port); + response.GlobalConfiguration.ServiceDiscoveryProvider.Provider.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Provider); + + for (var i = 0; i < response.ReRoutes.Count; i++) + { + response.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost); + response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate); + response.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort); + response.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme); + response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expected.ReRoutes[i].UpstreamPathTemplate); + response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expected.ReRoutes[i].UpstreamHttpMethod); + } + } + + private void GivenIHaveAddedATokenToMyRequest() + { + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); + } + + private void GivenIHaveAnOcelotToken(string adminPath) + { + var tokenUrl = $"{adminPath}/connect/token"; + var formData = new List> + { + new KeyValuePair("client_id", "admin"), + new KeyValuePair("client_secret", "secret"), + new KeyValuePair("scope", "admin"), + new KeyValuePair("username", "admin"), + new KeyValuePair("password", "secret"), + new KeyValuePair("grant_type", "password") + }; + var content = new FormUrlEncodedContent(formData); + + var response = _httpClient.PostAsync(tokenUrl, content).Result; + var responseContent = response.Content.ReadAsStringAsync().Result; + response.EnsureSuccessStatusCode(); + _token = JsonConvert.DeserializeObject(responseContent); + } + + private void GivenOcelotIsRunning() + { + _webHostBuilder = new WebHostBuilder() + .UseUrls(_ocelotBaseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureServices(x => + { + x.AddSingleton(_webHostBuilder); + }) + .UseStartup(); + + _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 { 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; } + } + } +} diff --git a/test/Ocelot.IntegrationTests/configuration.json b/test/Ocelot.IntegrationTests/configuration.json index 2ce0beff..6c4e9ae7 100755 --- a/test/Ocelot.IntegrationTests/configuration.json +++ b/test/Ocelot.IntegrationTests/configuration.json @@ -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}}} \ No newline at end of file +{"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}}} \ No newline at end of file From f0dcefff38ea346234669252ed075cbc0a64b341 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 6 Mar 2017 07:34:07 +0000 Subject: [PATCH 4/8] test fails on my mac.. --- test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs index c8792f5d..25b7acb0 100644 --- a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs +++ b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs @@ -63,7 +63,7 @@ namespace Ocelot.IntegrationTests this.Given(x => GivenThereIsAConfiguration(configuration)) .And(x => GivenThereIsAServiceRunningOn("http://localhost:51879")) .And(x => GivenOcelotIsRunning()) - .When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 50)) + .When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 100)) .Then(x => ThenTheSameHeaderValuesAreReturnedByTheDownstreamService()) .BDDfy(); } From f44357518517878a02e01ff4584906ef17ba978d Mon Sep 17 00:00:00 2001 From: "tom.pallister" Date: Mon, 6 Mar 2017 13:24:36 +0000 Subject: [PATCH 5/8] thread safe test passing on windows --- .../AdministrationTests.cs | 1 + .../ThreadSafeHeadersTests.cs | 60 ++----------------- 2 files changed, 5 insertions(+), 56 deletions(-) diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs index 210c7ac5..2f78e795 100644 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -13,6 +13,7 @@ using Shouldly; using TestStack.BDDfy; using Xunit; +[assembly: CollectionBehavior(DisableTestParallelization = true)] namespace Ocelot.IntegrationTests { public class AdministrationTests : IDisposable diff --git a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs index 25b7acb0..191dea47 100644 --- a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs +++ b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs @@ -30,14 +30,14 @@ namespace Ocelot.IntegrationTests private BearerToken _token; private IWebHost _downstreamBuilder; private readonly Random _random; - private ConcurrentBag _results; + private readonly ConcurrentBag _results; public ThreadSafeHeadersTests() { _results = new ConcurrentBag(); _random = new Random(); _httpClient = new HttpClient(); - _ocelotBaseUrl = "http://localhost:5000"; + _ocelotBaseUrl = "http://localhost:5001"; _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); } @@ -63,10 +63,11 @@ namespace Ocelot.IntegrationTests this.Given(x => GivenThereIsAConfiguration(configuration)) .And(x => GivenThereIsAServiceRunningOn("http://localhost:51879")) .And(x => GivenOcelotIsRunning()) - .When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 100)) + .When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 300)) .Then(x => ThenTheSameHeaderValuesAreReturnedByTheDownstreamService()) .BDDfy(); } + private void GivenThereIsAServiceRunningOn(string url) { _downstreamBuilder = new WebHostBuilder() @@ -89,59 +90,6 @@ namespace Ocelot.IntegrationTests _downstreamBuilder.Start(); } - private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration) - { - var json = JsonConvert.SerializeObject(updatedConfiguration); - var content = new StringContent(json); - content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - _response = _httpClient.PostAsync(url, content).Result; - } - - private void ThenTheResponseShouldBe(FileConfiguration expected) - { - var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); - - response.GlobalConfiguration.AdministrationPath.ShouldBe(expected.GlobalConfiguration.AdministrationPath); - response.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey); - response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host); - response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port); - response.GlobalConfiguration.ServiceDiscoveryProvider.Provider.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Provider); - - for (var i = 0; i < response.ReRoutes.Count; i++) - { - response.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost); - response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate); - response.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort); - response.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme); - response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expected.ReRoutes[i].UpstreamPathTemplate); - response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expected.ReRoutes[i].UpstreamHttpMethod); - } - } - - private void GivenIHaveAddedATokenToMyRequest() - { - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - } - - private void GivenIHaveAnOcelotToken(string adminPath) - { - var tokenUrl = $"{adminPath}/connect/token"; - var formData = new List> - { - new KeyValuePair("client_id", "admin"), - new KeyValuePair("client_secret", "secret"), - new KeyValuePair("scope", "admin"), - new KeyValuePair("username", "admin"), - new KeyValuePair("password", "secret"), - new KeyValuePair("grant_type", "password") - }; - var content = new FormUrlEncodedContent(formData); - - var response = _httpClient.PostAsync(tokenUrl, content).Result; - var responseContent = response.Content.ReadAsStringAsync().Result; - response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); - } private void GivenOcelotIsRunning() { From 23ad6ed2642fce94b9a5d9d7be4e52b67a3a3a91 Mon Sep 17 00:00:00 2001 From: geffzhang Date: Tue, 7 Mar 2017 08:29:03 +0800 Subject: [PATCH 6/8] refactor: HttpClientCache and remove not used code current --- global.json | 2 +- .../ServiceCollectionExtensions.cs | 2 +- src/Ocelot/Requester/HttpClientBuilder.cs | 69 +++++-------------- .../Requester/HttpClientHttpRequester.cs | 35 ++++++---- src/Ocelot/Requester/IHttpClientBuilder.cs | 21 +----- ...ageCacheHandler.cs => IHttpClientCache.cs} | 2 +- ...cheHandler.cs => MemoryHttpClientCache.cs} | 8 +-- 7 files changed, 48 insertions(+), 91 deletions(-) rename src/Ocelot/Requester/{IHttpClientMessageCacheHandler.cs => IHttpClientCache.cs} (86%) rename src/Ocelot/Requester/{MemoryHttpClientMessageCacheHandler.cs => MemoryHttpClientCache.cs} (70%) diff --git a/global.json b/global.json index ff8d898e..616b2c4e 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@  { "projects": [ "src", "test" ], "sdk": { - "version": "1.0.0-preview2-003133" + "version": "1.0.0-preview2-003131" } } diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 04ca1037..dbb98810 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -135,7 +135,7 @@ namespace Ocelot.DependencyInjection services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); // 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 diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index 8cad9d49..d7939df0 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -11,61 +11,23 @@ namespace Ocelot.Requester { internal class HttpClientBuilder : IHttpClientBuilder { - private TimeSpan? _timeout; - private readonly List _handlers = new List(); + private readonly Dictionary> _handlers = new Dictionary>(); private Dictionary _defaultHeaders; - private CookieContainer _cookieContainer; - private IQoSProvider _qoSProvider; - public IHttpClientBuilder WithCookieContainer(CookieContainer cookieContainer) + public IHttpClientBuilder WithQos(IQoSProvider qosProvider, IOcelotLogger logger) { - _cookieContainer = cookieContainer; + _handlers.Add(5000, () => new PollyCircuitBreakingDelegatingHandler(qosProvider, logger)); return this; - } - public IHttpClientBuilder WithTimeout(TimeSpan timeout) - { - _timeout = timeout; - return this; - } - - public IHttpClientBuilder WithHandler(DelegatingHandler handler) - { - _handlers.Add(handler); - return this; - } - - public IHttpClientBuilder WithDefaultRequestHeaders(Dictionary headers) - { - _defaultHeaders = headers; - return this; - } + } public IHttpClient Create() { - HttpClientHandler httpclientHandler = null; - if (_cookieContainer != null) - { - httpclientHandler = new HttpClientHandler() { CookieContainer = _cookieContainer }; - } - else - { - httpclientHandler = new HttpClientHandler(); - } - - if (httpclientHandler.SupportsAutomaticDecompression) - { - httpclientHandler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; - } - + var httpclientHandler = new HttpClientHandler(); + var client = new HttpClient(CreateHttpMessageHandler(httpclientHandler)); - - if (_timeout.HasValue) - { - client.Timeout = _timeout.Value; - } - + if (_defaultHeaders == null) { return new HttpClientWrapper(client); @@ -81,11 +43,18 @@ namespace Ocelot.Requester private HttpMessageHandler CreateHttpMessageHandler(HttpMessageHandler httpMessageHandler) { - foreach (var handler in _handlers) - { - handler.InnerHandler = httpMessageHandler; - httpMessageHandler = handler; - } + + _handlers + .OrderByDescending(handler => handler.Key) + .Select(handler => handler.Value) + .Reverse() + .ToList() + .ForEach(handler => + { + var delegatingHandler = handler(); + delegatingHandler.InnerHandler = httpMessageHandler; + httpMessageHandler = delegatingHandler; + }); return httpMessageHandler; } } diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index f0566fed..06185b75 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -11,10 +11,10 @@ namespace Ocelot.Requester { public class HttpClientHttpRequester : IHttpRequester { - private IHttpClientMessageCacheHandler _cacheHandlers; + private readonly IHttpClientCache _cacheHandlers; private readonly IOcelotLogger _logger; - public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory, IHttpClientMessageCacheHandler cacheHandlers) + public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory, IHttpClientCache cacheHandlers) { _logger = loggerFactory.CreateLogger(); _cacheHandlers = cacheHandlers; @@ -24,23 +24,15 @@ namespace Ocelot.Requester { var builder = new HttpClientBuilder(); - builder.WithCookieContainer(request.CookieContainer); + var cacheKey = GetCacheKey(request, builder); - string baseUrl = $"{request.HttpRequestMessage.RequestUri.Scheme}://{request.HttpRequestMessage.RequestUri.Authority}"; - - if (request.IsQos) - { - builder.WithHandler(new PollyCircuitBreakingDelegatingHandler(request.QosProvider, _logger)); - baseUrl = $"{baseUrl}{request.QosProvider.CircuitBreaker.CircuitBreakerPolicy.PolicyKey}"; - } - - IHttpClient httpClient = _cacheHandlers.Get(baseUrl); + var httpClient = _cacheHandlers.Get(cacheKey); if (httpClient == null) { httpClient = builder.Create(); - _cacheHandlers.Set(baseUrl, httpClient, TimeSpan.FromMinutes(30)); - } - + _cacheHandlers.Set(cacheKey, httpClient, TimeSpan.FromHours(6)); + } + try { var response = await httpClient.SendAsync(request.HttpRequestMessage); @@ -62,5 +54,18 @@ namespace Ocelot.Requester } } + + private string GetCacheKey(Request.Request request, HttpClientBuilder 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; + } } } \ No newline at end of file diff --git a/src/Ocelot/Requester/IHttpClientBuilder.cs b/src/Ocelot/Requester/IHttpClientBuilder.cs index 85fd5f69..832fd8d1 100644 --- a/src/Ocelot/Requester/IHttpClientBuilder.cs +++ b/src/Ocelot/Requester/IHttpClientBuilder.cs @@ -11,28 +11,11 @@ namespace Ocelot.Requester { public interface IHttpClientBuilder { - /// - /// Sets the cookie container used to store server cookies by the handler - /// - /// - /// - IHttpClientBuilder WithCookieContainer(CookieContainer cookieContainer); /// - /// Sets the number of milliseconds to wait before the request times out. + /// Sets a PollyCircuitBreakingDelegatingHandler . /// - IHttpClientBuilder WithTimeout(TimeSpan timeout); - - /// - /// Sets a DelegatingHandler. - /// Can be reused to set several different Handlers in a pipeline. - /// - IHttpClientBuilder WithHandler(DelegatingHandler handler); - - /// - /// Sets Default HttpRequestHeaders - /// - IHttpClientBuilder WithDefaultRequestHeaders(Dictionary headers); + IHttpClientBuilder WithQos(IQoSProvider qosProvider, IOcelotLogger logger); /// /// Creates the diff --git a/src/Ocelot/Requester/IHttpClientMessageCacheHandler.cs b/src/Ocelot/Requester/IHttpClientCache.cs similarity index 86% rename from src/Ocelot/Requester/IHttpClientMessageCacheHandler.cs rename to src/Ocelot/Requester/IHttpClientCache.cs index a2a93692..f4b8f76e 100644 --- a/src/Ocelot/Requester/IHttpClientMessageCacheHandler.cs +++ b/src/Ocelot/Requester/IHttpClientCache.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace Ocelot.Requester { - public interface IHttpClientMessageCacheHandler + public interface IHttpClientCache { bool Exists(string id); IHttpClient Get(string id); diff --git a/src/Ocelot/Requester/MemoryHttpClientMessageCacheHandler.cs b/src/Ocelot/Requester/MemoryHttpClientCache.cs similarity index 70% rename from src/Ocelot/Requester/MemoryHttpClientMessageCacheHandler.cs rename to src/Ocelot/Requester/MemoryHttpClientCache.cs index bb948b8a..33ff15a1 100644 --- a/src/Ocelot/Requester/MemoryHttpClientMessageCacheHandler.cs +++ b/src/Ocelot/Requester/MemoryHttpClientCache.cs @@ -7,18 +7,18 @@ using System.Threading.Tasks; namespace Ocelot.Requester { - public class MemoryHttpClientMessageCacheHandler : IHttpClientMessageCacheHandler + public class MemoryHttpClientCache : IHttpClientCache { private readonly IMemoryCache _memoryCache; - public MemoryHttpClientMessageCacheHandler(IMemoryCache memoryCache) + public MemoryHttpClientCache(IMemoryCache memoryCache) { _memoryCache = memoryCache; } - public void Set(string id, IHttpClient counter, TimeSpan expirationTime) + public void Set(string id, IHttpClient client, TimeSpan expirationTime) { - _memoryCache.Set(id, counter, new MemoryCacheEntryOptions().SetAbsoluteExpiration(expirationTime)); + _memoryCache.Set(id, client, new MemoryCacheEntryOptions().SetAbsoluteExpiration(expirationTime)); } public bool Exists(string id) From 57285309429f0057e044bfa9dcd160a9f5ab133b Mon Sep 17 00:00:00 2001 From: geffzhang Date: Tue, 7 Mar 2017 08:41:28 +0800 Subject: [PATCH 7/8] Update global.json revert global.json sdk to 3133 --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index 616b2c4e..ff8d898e 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@  { "projects": [ "src", "test" ], "sdk": { - "version": "1.0.0-preview2-003131" + "version": "1.0.0-preview2-003133" } } From ffd4e364f6b1f291023e003c6fda27408bee0baa Mon Sep 17 00:00:00 2001 From: "tom.pallister" Date: Wed, 8 Mar 2017 13:10:44 +0000 Subject: [PATCH 8/8] removed default headers --- src/Ocelot/Requester/HttpClientBuilder.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index 3af3b9be..141eab8b 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -12,8 +12,6 @@ namespace Ocelot.Requester internal class HttpClientBuilder : IHttpClientBuilder { private readonly Dictionary> _handlers = new Dictionary>(); - private Dictionary _defaultHeaders; - public IHttpClientBuilder WithQos(IQoSProvider qosProvider, IOcelotLogger logger) { @@ -28,16 +26,6 @@ namespace Ocelot.Requester var client = new HttpClient(CreateHttpMessageHandler(httpclientHandler)); - if (_defaultHeaders == null) - { - return new HttpClientWrapper(client); - } - - foreach (var header in _defaultHeaders) - { - client.DefaultRequestHeaders.Add(header.Key, header.Value); - } - return new HttpClientWrapper(client); }