From ef3c4f614aacdb1b16106b28070c03231aa04ce9 Mon Sep 17 00:00:00 2001 From: geffzhang Date: Tue, 13 Feb 2018 02:33:23 +0800 Subject: [PATCH] Monitoring (#219) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: use Https://github.com/ButterflyAPM to monitor each API request monitoring metrics * feat: using DiagnosticSource and Butterfly.OpenTracing * refactor:refactor Ocelot tracing, merge code into OcelotDiagnosticListener * refactor: move OcelotHttpTracingHandler to Requester * fix: Requester\HttpClientBuilder.cs(10,14): error CS0234: The type or namespace name 'Tracing' does not exist in the namespace * feat: add test should_set_up_tracing * feat : Remove extraneous code * feat: remove unused DiagnosticSource diagnostic * fix : test UseTracing * add test should_call_scoped_data_repository_QosProviderError * add test should_return_any_errors * add test HttpClientHttpRequesterTest * it should keep it can not be deleted --- Ocelot.sln | 2 +- .../Creator/HttpHandlerOptionsCreator.cs | 2 +- .../File/FileHttpHandlerOptions.cs | 2 + .../Configuration/HttpHandlerOptions.cs | 8 +- .../DependencyInjection/IOcelotBuilder.cs | 26 +- .../DependencyInjection/OcelotBuilder.cs | 620 +++++++++--------- .../Logging/OcelotDiagnosticListener.cs | 10 + src/Ocelot/Ocelot.csproj | 91 +-- .../Request/Builder/HttpRequestCreator.cs | 41 +- src/Ocelot/Request/Builder/IRequestCreator.cs | 37 +- .../HttpRequestBuilderMiddleware.cs | 131 ++-- src/Ocelot/Request/Request.cs | 60 +- src/Ocelot/Requester/HttpClientBuilder.cs | 141 ++-- .../Requester/HttpClientHttpRequester.cs | 167 ++--- src/Ocelot/Requester/IHttpClientBuilder.cs | 2 +- src/Ocelot/Requester/IHttpRequester.cs | 2 - .../Middleware/HttpRequesterMiddleware.cs | 10 +- .../Requester/OcelotHttpTracingHandler.cs | 62 ++ .../Middleware/ResponderMiddleware.cs | 16 +- test/Ocelot.IntegrationTests/RaftStartup.cs | 105 +-- test/Ocelot.ManualTest/ManualTestStartup.cs | 85 +-- test/Ocelot.ManualTest/configuration.json | 591 +++++++++-------- .../FileConfigurationCreatorTests.cs | 2 +- .../HttpHandlerOptionsCreatorTests.cs | 8 +- .../DependencyInjection/OcelotBuilderTests.cs | 52 +- .../HttpRequestBuilderMiddlewareTests.cs | 36 +- .../Request/HttpRequestCreatorTests.cs | 153 ++--- .../Requester/HttpClientHttpRequesterTest.cs | 76 +++ .../Requester/HttpRequesterMiddlewareTests.cs | 162 ++--- .../Responder/ResponderMiddlewareTests.cs | 24 + 30 files changed, 1513 insertions(+), 1211 deletions(-) create mode 100644 src/Ocelot/Requester/OcelotHttpTracingHandler.cs create mode 100644 test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs diff --git a/Ocelot.sln b/Ocelot.sln index 82a17db9..fb7008d3 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26730.15 +VisualStudioVersion = 15.0.27130.2024 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5CFB79B7-C9DC-45A4-9A75-625D92471702}" EndProject diff --git a/src/Ocelot/Configuration/Creator/HttpHandlerOptionsCreator.cs b/src/Ocelot/Configuration/Creator/HttpHandlerOptionsCreator.cs index 52aa7694..6c66f3c0 100644 --- a/src/Ocelot/Configuration/Creator/HttpHandlerOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/HttpHandlerOptionsCreator.cs @@ -7,7 +7,7 @@ namespace Ocelot.Configuration.Creator public HttpHandlerOptions Create(FileReRoute fileReRoute) { return new HttpHandlerOptions(fileReRoute.HttpHandlerOptions.AllowAutoRedirect, - fileReRoute.HttpHandlerOptions.UseCookieContainer); + fileReRoute.HttpHandlerOptions.UseCookieContainer, fileReRoute.HttpHandlerOptions.UseTracing); } } } diff --git a/src/Ocelot/Configuration/File/FileHttpHandlerOptions.cs b/src/Ocelot/Configuration/File/FileHttpHandlerOptions.cs index ea352767..7f24b572 100644 --- a/src/Ocelot/Configuration/File/FileHttpHandlerOptions.cs +++ b/src/Ocelot/Configuration/File/FileHttpHandlerOptions.cs @@ -11,5 +11,7 @@ public bool AllowAutoRedirect { get; set; } public bool UseCookieContainer { get; set; } + + public bool UseTracing { get; set; } } } diff --git a/src/Ocelot/Configuration/HttpHandlerOptions.cs b/src/Ocelot/Configuration/HttpHandlerOptions.cs index 0ec72d1d..ef0edd9c 100644 --- a/src/Ocelot/Configuration/HttpHandlerOptions.cs +++ b/src/Ocelot/Configuration/HttpHandlerOptions.cs @@ -6,10 +6,11 @@ /// public class HttpHandlerOptions { - public HttpHandlerOptions(bool allowAutoRedirect, bool useCookieContainer) + public HttpHandlerOptions(bool allowAutoRedirect, bool useCookieContainer, bool useTracing) { AllowAutoRedirect = allowAutoRedirect; UseCookieContainer = useCookieContainer; + UseTracing = useTracing; } /// @@ -21,5 +22,10 @@ /// Specify is handler has to use a cookie container /// public bool UseCookieContainer { get; private set; } + + // + /// Specify is handler has to use a opentracing + /// + public bool UseTracing { get; private set; } } } diff --git a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs index d7cb0f0d..4519364b 100644 --- a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs @@ -1,12 +1,14 @@ -using CacheManager.Core; -using System; - -namespace Ocelot.DependencyInjection -{ - public interface IOcelotBuilder - { - IOcelotBuilder AddStoreOcelotConfigurationInConsul(); - IOcelotBuilder AddCacheManager(Action settings); - IOcelotAdministrationBuilder AddAdministration(string path, string secret); - } -} +using Butterfly.Client.AspNetCore; +using CacheManager.Core; +using System; + +namespace Ocelot.DependencyInjection +{ + public interface IOcelotBuilder + { + IOcelotBuilder AddStoreOcelotConfigurationInConsul(); + IOcelotBuilder AddCacheManager(Action settings); + IOcelotBuilder AddOpenTracing(Action settings); + IOcelotAdministrationBuilder AddAdministration(string path, string secret); + } +} diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index a923071b..308cea14 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -1,307 +1,313 @@ -using CacheManager.Core; -using IdentityServer4.Models; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Ocelot.Authorisation; -using Ocelot.Cache; -using Ocelot.Claims; -using Ocelot.Configuration.Authentication; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Parser; -using Ocelot.Configuration.Provider; -using Ocelot.Configuration.Repository; -using Ocelot.Configuration.Setter; -using Ocelot.Configuration.Validator; -using Ocelot.DownstreamRouteFinder.Finder; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.DownstreamUrlCreator; -using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; -using Ocelot.Headers; -using Ocelot.Infrastructure.Claims.Parser; -using Ocelot.Infrastructure.RequestData; -using Ocelot.LoadBalancer.LoadBalancers; -using Ocelot.Logging; -using Ocelot.Middleware; -using Ocelot.QueryStrings; -using Ocelot.RateLimit; -using Ocelot.Request.Builder; -using Ocelot.Request.Mapper; -using Ocelot.Requester; -using Ocelot.Requester.QoS; -using Ocelot.Responder; -using Ocelot.ServiceDiscovery; -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.Net.Http; -using System.Reflection; -using System.Security.Cryptography.X509Certificates; -using IdentityServer4.AccessTokenValidation; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using FileConfigurationProvider = Ocelot.Configuration.Provider.FileConfigurationProvider; -using Microsoft.Extensions.DependencyInjection.Extensions; -using System.Linq; -using Ocelot.Raft; -using Rafty.Concensus; -using Rafty.FiniteStateMachine; -using Rafty.Infrastructure; -using Rafty.Log; -using Newtonsoft.Json; - -namespace Ocelot.DependencyInjection -{ - public class OcelotBuilder : IOcelotBuilder - { - private IServiceCollection _services; - private IConfiguration _configurationRoot; - - public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot) - { - _configurationRoot = configurationRoot; - _services = services; - - //add default cache settings... - Action defaultCachingSettings = x => - { - x.WithDictionaryHandle(); - }; - - AddCacheManager(defaultCachingSettings); - - //add ocelot services... - _services.Configure(configurationRoot); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - // 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 - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.AddMemoryCache(); - _services.TryAddSingleton(); - - //add asp.net services.. - var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly; - - _services.AddMvcCore() - .AddApplicationPart(assembly) - .AddControllersAsServices() - .AddAuthorization() - .AddJsonFormatters(); - - _services.AddLogging(); - _services.AddMiddlewareAnalysis(); - _services.AddWebEncoders(); - _services.AddSingleton(new NullAdministrationPath()); - } - - public IOcelotAdministrationBuilder AddAdministration(string path, string secret) - { - var administrationPath = new AdministrationPath(path); - - //add identity server for admin area - var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(secret); - - if (identityServerConfiguration != null) - { - AddIdentityServer(identityServerConfiguration, administrationPath); - } - - var descriptor = new ServiceDescriptor(typeof(IAdministrationPath), administrationPath); - _services.Replace(descriptor); - return new OcelotAdministrationBuilder(_services, _configurationRoot); - } - - public IOcelotBuilder AddStoreOcelotConfigurationInConsul() - { - var serviceDiscoveryPort = _configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Port", 0); - var serviceDiscoveryHost = _configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Host", string.Empty); - - var config = new ServiceProviderConfigurationBuilder() - .WithServiceDiscoveryProviderPort(serviceDiscoveryPort) - .WithServiceDiscoveryProviderHost(serviceDiscoveryHost) - .Build(); - - _services.AddSingleton(config); - _services.AddSingleton(); - _services.AddSingleton(); - return this; - } - - public IOcelotBuilder AddCacheManager(Action settings) - { - var cacheManagerOutputCache = CacheFactory.Build("OcelotOutputCache", settings); - var ocelotOutputCacheManager = new OcelotCacheManagerCache(cacheManagerOutputCache); - - _services.RemoveAll(typeof(ICacheManager)); - _services.RemoveAll(typeof(IOcelotCache)); - _services.AddSingleton>(cacheManagerOutputCache); - _services.AddSingleton>(ocelotOutputCacheManager); - - var ocelotConfigCacheManagerOutputCache = CacheFactory.Build("OcelotConfigurationCache", settings); - var ocelotConfigCacheManager = new OcelotCacheManagerCache(ocelotConfigCacheManagerOutputCache); - _services.RemoveAll(typeof(ICacheManager)); - _services.RemoveAll(typeof(IOcelotCache)); - _services.AddSingleton>(ocelotConfigCacheManagerOutputCache); - _services.AddSingleton>(ocelotConfigCacheManager); - - var fileConfigCacheManagerOutputCache = CacheFactory.Build("FileConfigurationCache", settings); - var fileConfigCacheManager = new OcelotCacheManagerCache(fileConfigCacheManagerOutputCache); - _services.RemoveAll(typeof(ICacheManager)); - _services.RemoveAll(typeof(IOcelotCache)); - _services.AddSingleton>(fileConfigCacheManagerOutputCache); - _services.AddSingleton>(fileConfigCacheManager); - return this; - } - - private void AddIdentityServer(IIdentityServerConfiguration identityServerConfiguration, IAdministrationPath adminPath) - { - _services.TryAddSingleton(identityServerConfiguration); - _services.TryAddSingleton(); - var identityServerBuilder = _services - .AddIdentityServer(o => { - o.IssuerUri = "Ocelot"; - }) - .AddInMemoryApiResources(Resources(identityServerConfiguration)) - .AddInMemoryClients(Client(identityServerConfiguration)); - - //todo - refactor a method so we know why this is happening - var whb = _services.First(x => x.ServiceType == typeof(IWebHostBuilder)); - var urlFinder = new BaseUrlFinder((IWebHostBuilder)whb.ImplementationInstance); - var baseSchemeUrlAndPort = urlFinder.Find(); - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - - _services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) - .AddIdentityServerAuthentication(o => - { - o.Authority = baseSchemeUrlAndPort + adminPath.Path; - o.ApiName = identityServerConfiguration.ApiName; - o.RequireHttpsMetadata = identityServerConfiguration.RequireHttps; - o.SupportedTokens = SupportedTokens.Both; - o.ApiSecret = identityServerConfiguration.ApiSecret; - }); - - //todo - refactor naming.. - if (string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificateLocation) || string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificatePassword)) - { - identityServerBuilder.AddDeveloperSigningCredential(); - } - else - { - //todo - refactor so calls method? - var cert = new X509Certificate2(identityServerConfiguration.CredentialsSigningCertificateLocation, identityServerConfiguration.CredentialsSigningCertificatePassword); - identityServerBuilder.AddSigningCredential(cert); - } - } - - private List Resources(IIdentityServerConfiguration identityServerConfiguration) - { - return new List - { - new ApiResource(identityServerConfiguration.ApiName, identityServerConfiguration.ApiName) - { - ApiSecrets = new List - { - new Secret - { - Value = identityServerConfiguration.ApiSecret.Sha256() - } - } - }, - }; - } - - private List Client(IIdentityServerConfiguration identityServerConfiguration) - { - return new List - { - new Client - { - ClientId = identityServerConfiguration.ApiName, - AllowedGrantTypes = GrantTypes.ClientCredentials, - ClientSecrets = new List {new Secret(identityServerConfiguration.ApiSecret.Sha256())}, - AllowedScopes = { identityServerConfiguration.ApiName } - } - }; - } - } - - public interface IOcelotAdministrationBuilder - { - IOcelotAdministrationBuilder AddRafty(); - } - - public class OcelotAdministrationBuilder : IOcelotAdministrationBuilder - { - private IServiceCollection _services; - private IConfiguration _configurationRoot; - - public OcelotAdministrationBuilder(IServiceCollection services, IConfiguration configurationRoot) - { - _configurationRoot = configurationRoot; - _services = services; - } - - public IOcelotAdministrationBuilder AddRafty() - { - var settings = new InMemorySettings(4000, 5000, 100, 5000); - _services.AddSingleton(); - _services.AddSingleton(); - _services.AddSingleton(settings); - _services.AddSingleton(); - _services.AddSingleton(); - _services.Configure(_configurationRoot); - return this; - } - } -} +using Butterfly.Client.AspNetCore; +using CacheManager.Core; +using IdentityServer4.AccessTokenValidation; +using IdentityServer4.Models; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Ocelot.Authorisation; +using Ocelot.Cache; +using Ocelot.Claims; +using Ocelot.Configuration; +using Ocelot.Configuration.Authentication; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Parser; +using Ocelot.Configuration.Provider; +using Ocelot.Configuration.Repository; +using Ocelot.Configuration.Setter; +using Ocelot.Configuration.Validator; +using Ocelot.DownstreamRouteFinder.Finder; +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.DownstreamUrlCreator; +using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; +using Ocelot.Headers; +using Ocelot.Infrastructure.Claims.Parser; +using Ocelot.Infrastructure.RequestData; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.Logging; +using Ocelot.Middleware; +using Ocelot.QueryStrings; +using Ocelot.Raft; +using Ocelot.RateLimit; +using Ocelot.Request.Builder; +using Ocelot.Request.Mapper; +using Ocelot.Requester; +using Ocelot.Requester.QoS; +using Ocelot.Responder; +using Ocelot.ServiceDiscovery; +using Rafty.Concensus; +using Rafty.FiniteStateMachine; +using Rafty.Infrastructure; +using Rafty.Log; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Reflection; +using System.Security.Cryptography.X509Certificates; +using FileConfigurationProvider = Ocelot.Configuration.Provider.FileConfigurationProvider; + +namespace Ocelot.DependencyInjection +{ + public class OcelotBuilder : IOcelotBuilder + { + private IServiceCollection _services; + private IConfiguration _configurationRoot; + + public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot) + { + _configurationRoot = configurationRoot; + _services = services; + + //add default cache settings... + Action defaultCachingSettings = x => + { + x.WithDictionaryHandle(); + }; + + AddCacheManager(defaultCachingSettings); + + //add ocelot services... + _services.Configure(configurationRoot); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + // 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 + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.AddMemoryCache(); + _services.TryAddSingleton(); + + //add asp.net services.. + var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly; + + _services.AddMvcCore() + .AddApplicationPart(assembly) + .AddControllersAsServices() + .AddAuthorization() + .AddJsonFormatters(); + + _services.AddLogging(); + _services.AddMiddlewareAnalysis(); + _services.AddWebEncoders(); + _services.AddSingleton(new NullAdministrationPath()); + } + + public IOcelotAdministrationBuilder AddAdministration(string path, string secret) + { + var administrationPath = new AdministrationPath(path); + + //add identity server for admin area + var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(secret); + + if (identityServerConfiguration != null) + { + AddIdentityServer(identityServerConfiguration, administrationPath); + } + + var descriptor = new ServiceDescriptor(typeof(IAdministrationPath), administrationPath); + _services.Replace(descriptor); + return new OcelotAdministrationBuilder(_services, _configurationRoot); + } + + public IOcelotBuilder AddStoreOcelotConfigurationInConsul() + { + var serviceDiscoveryPort = _configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Port", 0); + var serviceDiscoveryHost = _configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Host", string.Empty); + + var config = new ServiceProviderConfigurationBuilder() + .WithServiceDiscoveryProviderPort(serviceDiscoveryPort) + .WithServiceDiscoveryProviderHost(serviceDiscoveryHost) + .Build(); + + _services.AddSingleton(config); + _services.AddSingleton(); + _services.AddSingleton(); + return this; + } + + public IOcelotBuilder AddCacheManager(Action settings) + { + var cacheManagerOutputCache = CacheFactory.Build("OcelotOutputCache", settings); + var ocelotOutputCacheManager = new OcelotCacheManagerCache(cacheManagerOutputCache); + + _services.RemoveAll(typeof(ICacheManager)); + _services.RemoveAll(typeof(IOcelotCache)); + _services.AddSingleton>(cacheManagerOutputCache); + _services.AddSingleton>(ocelotOutputCacheManager); + + var ocelotConfigCacheManagerOutputCache = CacheFactory.Build("OcelotConfigurationCache", settings); + var ocelotConfigCacheManager = new OcelotCacheManagerCache(ocelotConfigCacheManagerOutputCache); + _services.RemoveAll(typeof(ICacheManager)); + _services.RemoveAll(typeof(IOcelotCache)); + _services.AddSingleton>(ocelotConfigCacheManagerOutputCache); + _services.AddSingleton>(ocelotConfigCacheManager); + + var fileConfigCacheManagerOutputCache = CacheFactory.Build("FileConfigurationCache", settings); + var fileConfigCacheManager = new OcelotCacheManagerCache(fileConfigCacheManagerOutputCache); + _services.RemoveAll(typeof(ICacheManager)); + _services.RemoveAll(typeof(IOcelotCache)); + _services.AddSingleton>(fileConfigCacheManagerOutputCache); + _services.AddSingleton>(fileConfigCacheManager); + return this; + } + + public IOcelotBuilder AddOpenTracing(Action settings) + { + _services.AddTransient(); + _services.AddButterfly(settings); + return this; + } + + private void AddIdentityServer(IIdentityServerConfiguration identityServerConfiguration, IAdministrationPath adminPath) + { + _services.TryAddSingleton(identityServerConfiguration); + _services.TryAddSingleton(); + var identityServerBuilder = _services + .AddIdentityServer(o => { + o.IssuerUri = "Ocelot"; + }) + .AddInMemoryApiResources(Resources(identityServerConfiguration)) + .AddInMemoryClients(Client(identityServerConfiguration)); + + //todo - refactor a method so we know why this is happening + var whb = _services.First(x => x.ServiceType == typeof(IWebHostBuilder)); + var urlFinder = new BaseUrlFinder((IWebHostBuilder)whb.ImplementationInstance); + var baseSchemeUrlAndPort = urlFinder.Find(); + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + + _services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) + .AddIdentityServerAuthentication(o => + { + o.Authority = baseSchemeUrlAndPort + adminPath.Path; + o.ApiName = identityServerConfiguration.ApiName; + o.RequireHttpsMetadata = identityServerConfiguration.RequireHttps; + o.SupportedTokens = SupportedTokens.Both; + o.ApiSecret = identityServerConfiguration.ApiSecret; + }); + + //todo - refactor naming.. + if (string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificateLocation) || string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificatePassword)) + { + identityServerBuilder.AddDeveloperSigningCredential(); + } + else + { + //todo - refactor so calls method? + var cert = new X509Certificate2(identityServerConfiguration.CredentialsSigningCertificateLocation, identityServerConfiguration.CredentialsSigningCertificatePassword); + identityServerBuilder.AddSigningCredential(cert); + } + } + + private List Resources(IIdentityServerConfiguration identityServerConfiguration) + { + return new List + { + new ApiResource(identityServerConfiguration.ApiName, identityServerConfiguration.ApiName) + { + ApiSecrets = new List + { + new Secret + { + Value = identityServerConfiguration.ApiSecret.Sha256() + } + } + }, + }; + } + + private List Client(IIdentityServerConfiguration identityServerConfiguration) + { + return new List + { + new Client + { + ClientId = identityServerConfiguration.ApiName, + AllowedGrantTypes = GrantTypes.ClientCredentials, + ClientSecrets = new List {new Secret(identityServerConfiguration.ApiSecret.Sha256())}, + AllowedScopes = { identityServerConfiguration.ApiName } + } + }; + } + } + + public interface IOcelotAdministrationBuilder + { + IOcelotAdministrationBuilder AddRafty(); + } + + public class OcelotAdministrationBuilder : IOcelotAdministrationBuilder + { + private IServiceCollection _services; + private IConfiguration _configurationRoot; + + public OcelotAdministrationBuilder(IServiceCollection services, IConfiguration configurationRoot) + { + _configurationRoot = configurationRoot; + _services = services; + } + + public IOcelotAdministrationBuilder AddRafty() + { + var settings = new InMemorySettings(4000, 5000, 100, 5000); + _services.AddSingleton(); + _services.AddSingleton(); + _services.AddSingleton(settings); + _services.AddSingleton(); + _services.AddSingleton(); + _services.Configure(_configurationRoot); + return this; + } + } +} diff --git a/src/Ocelot/Logging/OcelotDiagnosticListener.cs b/src/Ocelot/Logging/OcelotDiagnosticListener.cs index 53d6e14c..ee0b5b8a 100644 --- a/src/Ocelot/Logging/OcelotDiagnosticListener.cs +++ b/src/Ocelot/Logging/OcelotDiagnosticListener.cs @@ -1,6 +1,8 @@ using System; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DiagnosticAdapter; +using Butterfly.Client.AspNetCore; +using Butterfly.OpenTracing; namespace Ocelot.Logging { @@ -17,6 +19,7 @@ namespace Ocelot.Logging public virtual void OnMiddlewareStarting(HttpContext httpContext, string name) { _logger.LogTrace($"MiddlewareStarting: {name}; {httpContext.Request.Path}"); + Event(httpContext, $"MiddlewareStarting: {name}; {httpContext.Request.Path}"); } [DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareException")] @@ -29,6 +32,13 @@ namespace Ocelot.Logging public virtual void OnMiddlewareFinished(HttpContext httpContext, string name) { _logger.LogTrace($"MiddlewareFinished: {name}; {httpContext.Response.StatusCode}"); + Event(httpContext, $"MiddlewareFinished: {name}; {httpContext.Response.StatusCode}"); + } + + private void Event(HttpContext httpContext, string @event) + { + var span = httpContext.GetSpan(); + span?.Log(LogField.CreateNew().Event(@event)); } } } diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj index d2a00af1..eb4ce773 100644 --- a/src/Ocelot/Ocelot.csproj +++ b/src/Ocelot/Ocelot.csproj @@ -1,46 +1,47 @@ - - - netcoreapp2.0 - 2.0.0 - 2.0.0 - This project is aimed at people using .NET running a micro services / service orientated architecture that need a unified point of entry into their system. In particular I want easy integration with IdentityServer reference and bearer tokens. We have been unable to find this in my current workplace without having to write our own Javascript middlewares to handle the IdentityServer reference tokens. We would rather use the IdentityServer code that already exists to do this. Ocelot is a bunch of middlewares in a specific order. Ocelot manipulates the HttpRequest object into a state specified by its configuration until it reaches a request builder middleware where it creates a HttpRequestMessage object which is used to make a request to a downstream service. The middleware that makes the request is the last thing in the Ocelot pipeline. It does not call the next middleware. The response from the downstream service is stored in a per request scoped repository and retrived as the requests goes back up the Ocelot pipeline. There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features. - Ocelot - 0.0.0-dev - Ocelot - Ocelot - API Gateway;.NET core - https://github.com/TomPallister/Ocelot - https://github.com/TomPallister/Ocelot - win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64 - false - false - True - false - Tom Pallister - - - full - True - - - - - - - - - - - - - - - - - - - - - - + + + netcoreapp2.0 + 2.0.0 + 2.0.0 + This project is aimed at people using .NET running a micro services / service orientated architecture that need a unified point of entry into their system. In particular I want easy integration with IdentityServer reference and bearer tokens. We have been unable to find this in my current workplace without having to write our own Javascript middlewares to handle the IdentityServer reference tokens. We would rather use the IdentityServer code that already exists to do this. Ocelot is a bunch of middlewares in a specific order. Ocelot manipulates the HttpRequest object into a state specified by its configuration until it reaches a request builder middleware where it creates a HttpRequestMessage object which is used to make a request to a downstream service. The middleware that makes the request is the last thing in the Ocelot pipeline. It does not call the next middleware. The response from the downstream service is stored in a per request scoped repository and retrived as the requests goes back up the Ocelot pipeline. There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features. + Ocelot + 0.0.0-dev + Ocelot + Ocelot + API Gateway;.NET core + https://github.com/TomPallister/Ocelot + https://github.com/TomPallister/Ocelot + win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64 + false + false + True + false + Tom Pallister + + + full + True + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Ocelot/Request/Builder/HttpRequestCreator.cs b/src/Ocelot/Request/Builder/HttpRequestCreator.cs index 9a7d0b76..8e76adb3 100644 --- a/src/Ocelot/Request/Builder/HttpRequestCreator.cs +++ b/src/Ocelot/Request/Builder/HttpRequestCreator.cs @@ -1,20 +1,21 @@ -using System.Threading.Tasks; -using Ocelot.Responses; -using Ocelot.Requester.QoS; -using System.Net.Http; - -namespace Ocelot.Request.Builder -{ - public sealed class HttpRequestCreator : IRequestCreator - { - public async Task> Build( - HttpRequestMessage httpRequestMessage, - bool isQos, - IQoSProvider qosProvider, - bool useCookieContainer, - bool allowAutoRedirect) - { - return new OkResponse(new Request(httpRequestMessage, isQos, qosProvider, allowAutoRedirect, useCookieContainer)); - } - } -} \ No newline at end of file +using System.Threading.Tasks; +using Ocelot.Responses; +using Ocelot.Requester.QoS; +using System.Net.Http; + +namespace Ocelot.Request.Builder +{ + public sealed class HttpRequestCreator : IRequestCreator + { + public async Task> Build( + HttpRequestMessage httpRequestMessage, + bool isQos, + IQoSProvider qosProvider, + bool useCookieContainer, + bool allowAutoRedirect, + bool isTracing) + { + return new OkResponse(new Request(httpRequestMessage, isQos, qosProvider, allowAutoRedirect, useCookieContainer, isTracing)); + } + } +} diff --git a/src/Ocelot/Request/Builder/IRequestCreator.cs b/src/Ocelot/Request/Builder/IRequestCreator.cs index ab678582..8bfc3f2f 100644 --- a/src/Ocelot/Request/Builder/IRequestCreator.cs +++ b/src/Ocelot/Request/Builder/IRequestCreator.cs @@ -1,18 +1,19 @@ -namespace Ocelot.Request.Builder -{ - using System.Net.Http; - using System.Threading.Tasks; - - using Ocelot.Requester.QoS; - using Ocelot.Responses; - - public interface IRequestCreator - { - Task> Build( - HttpRequestMessage httpRequestMessage, - bool isQos, - IQoSProvider qosProvider, - bool useCookieContainer, - bool allowAutoRedirect); - } -} +namespace Ocelot.Request.Builder +{ + using System.Net.Http; + using System.Threading.Tasks; + + using Ocelot.Requester.QoS; + using Ocelot.Responses; + + public interface IRequestCreator + { + Task> Build( + HttpRequestMessage httpRequestMessage, + bool isQos, + IQoSProvider qosProvider, + bool useCookieContainer, + bool allowAutoRedirect, + bool isTracing); + } +} diff --git a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs index dd1ad429..25b674e4 100644 --- a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs +++ b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs @@ -1,66 +1,65 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Middleware; -using Ocelot.Request.Builder; -using Ocelot.Requester.QoS; - -namespace Ocelot.Request.Middleware -{ - public class HttpRequestBuilderMiddleware : OcelotMiddleware - { - private readonly RequestDelegate _next; - private readonly IRequestCreator _requestCreator; - private readonly IOcelotLogger _logger; - private readonly IQosProviderHouse _qosProviderHouse; - - public HttpRequestBuilderMiddleware(RequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IRequestScopedDataRepository requestScopedDataRepository, - IRequestCreator requestCreator, - IQosProviderHouse qosProviderHouse) - :base(requestScopedDataRepository) - { - _next = next; - _requestCreator = requestCreator; - _qosProviderHouse = qosProviderHouse; - _logger = loggerFactory.CreateLogger(); - } - - public async Task Invoke(HttpContext context) - { - var qosProvider = _qosProviderHouse.Get(DownstreamRoute.ReRoute); - - if (qosProvider.IsError) - { - _logger.LogDebug("IQosProviderHouse returned an error, setting pipeline error"); - - SetPipelineError(qosProvider.Errors); - - return; - } - - var buildResult = await _requestCreator.Build( - DownstreamRequest, - DownstreamRoute.ReRoute.IsQos, - qosProvider.Data, - DownstreamRoute.ReRoute.HttpHandlerOptions.UseCookieContainer, - DownstreamRoute.ReRoute.HttpHandlerOptions.AllowAutoRedirect); - - if (buildResult.IsError) - { - _logger.LogDebug("IRequestCreator returned an error, setting pipeline error"); - - SetPipelineError(buildResult.Errors); - - return; - } - _logger.LogDebug("setting upstream request"); - - SetUpstreamRequestForThisRequest(buildResult.Data); - - await _next.Invoke(context); - } - } -} \ No newline at end of file +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Ocelot.Infrastructure.RequestData; +using Ocelot.Logging; +using Ocelot.Middleware; +using Ocelot.Request.Builder; +using Ocelot.Requester.QoS; + +namespace Ocelot.Request.Middleware +{ + public class HttpRequestBuilderMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IRequestCreator _requestCreator; + private readonly IOcelotLogger _logger; + private readonly IQosProviderHouse _qosProviderHouse; + + public HttpRequestBuilderMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IRequestScopedDataRepository requestScopedDataRepository, + IRequestCreator requestCreator, + IQosProviderHouse qosProviderHouse) + :base(requestScopedDataRepository) + { + _next = next; + _requestCreator = requestCreator; + _qosProviderHouse = qosProviderHouse; + _logger = loggerFactory.CreateLogger(); + } + + public async Task Invoke(HttpContext context) + { + var qosProvider = _qosProviderHouse.Get(DownstreamRoute.ReRoute); + + if (qosProvider.IsError) + { + _logger.LogDebug("IQosProviderHouse returned an error, setting pipeline error"); + + SetPipelineError(qosProvider.Errors); + + return; + } + + var buildResult = await _requestCreator.Build( + DownstreamRequest, + DownstreamRoute.ReRoute.IsQos, + qosProvider.Data, + DownstreamRoute.ReRoute.HttpHandlerOptions.UseCookieContainer, + DownstreamRoute.ReRoute.HttpHandlerOptions.AllowAutoRedirect, + DownstreamRoute.ReRoute.HttpHandlerOptions.UseTracing); + if (buildResult.IsError) + { + _logger.LogDebug("IRequestCreator returned an error, setting pipeline error"); + SetPipelineError(buildResult.Errors); + return; + } + + _logger.LogDebug("setting upstream request"); + + SetUpstreamRequestForThisRequest(buildResult.Data); + + await _next.Invoke(context); + } + } +} diff --git a/src/Ocelot/Request/Request.cs b/src/Ocelot/Request/Request.cs index 79c170ea..3b003e57 100644 --- a/src/Ocelot/Request/Request.cs +++ b/src/Ocelot/Request/Request.cs @@ -1,28 +1,32 @@ -using System.Net.Http; -using Ocelot.Requester.QoS; - -namespace Ocelot.Request -{ - public class Request - { - public Request( - HttpRequestMessage httpRequestMessage, - bool isQos, - IQoSProvider qosProvider, - bool allowAutoRedirect, - bool useCookieContainer) - { - HttpRequestMessage = httpRequestMessage; - IsQos = isQos; - QosProvider = qosProvider; - AllowAutoRedirect = allowAutoRedirect; - UseCookieContainer = useCookieContainer; - } - - public HttpRequestMessage HttpRequestMessage { get; private set; } - public bool IsQos { get; private set; } - public IQoSProvider QosProvider { get; private set; } - public bool AllowAutoRedirect { get; private set; } - public bool UseCookieContainer { get; private set; } - } -} +using System.Net.Http; +using Ocelot.Requester.QoS; + +namespace Ocelot.Request +{ + public class Request + { + public Request( + HttpRequestMessage httpRequestMessage, + bool isQos, + IQoSProvider qosProvider, + bool allowAutoRedirect, + bool useCookieContainer, + bool isTracing + ) + { + HttpRequestMessage = httpRequestMessage; + IsQos = isQos; + QosProvider = qosProvider; + AllowAutoRedirect = allowAutoRedirect; + UseCookieContainer = useCookieContainer; + IsTracing = isTracing; + } + + public HttpRequestMessage HttpRequestMessage { get; private set; } + public bool IsQos { get; private set; } + public bool IsTracing { get; private set; } + public IQoSProvider QosProvider { get; private set; } + public bool AllowAutoRedirect { get; private set; } + public bool UseCookieContainer { get; private set; } + } +} diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index 257c4222..5f64ad6a 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -1,66 +1,75 @@ -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 : IHttpClientBuilder - { - private readonly Dictionary> _handlers = new Dictionary>(); - - public IHttpClientBuilder WithQos(IQoSProvider qosProvider, IOcelotLogger logger) - { - _handlers.Add(5000, () => new PollyCircuitBreakingDelegatingHandler(qosProvider, logger)); - - return this; - } - - public IHttpClient Create(bool useCookies, bool allowAutoRedirect) - { - var httpclientHandler = new HttpClientHandler { AllowAutoRedirect = allowAutoRedirect, UseCookies = useCookies}; - - var client = new HttpClient(CreateHttpMessageHandler(httpclientHandler)); - - return new HttpClientWrapper(client); - } - - 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; - }); - 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); - } - } -} +using Microsoft.Extensions.DependencyInjection; +using Ocelot.Logging; +using Ocelot.Requester.QoS; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Ocelot.Requester +{ + internal class HttpClientBuilder : IHttpClientBuilder + { + private readonly Dictionary> _handlers = new Dictionary>(); + + public IHttpClientBuilder WithQos(IQoSProvider qosProvider, IOcelotLogger logger) + { + _handlers.Add(5000, () => new PollyCircuitBreakingDelegatingHandler(qosProvider, logger)); + + return this; + } + + private IHttpClientBuilder WithTracing(IServiceProvider provider) + { + _handlers.Add(6000, () => provider.GetService()); + return this; + } + + public IHttpClient Create(bool useCookies, bool allowAutoRedirect, bool isTracing, IServiceProvider provider) + { + var httpclientHandler = new HttpClientHandler { AllowAutoRedirect = allowAutoRedirect, UseCookies = useCookies }; + if (isTracing) + { + WithTracing(provider); + } + var client = new HttpClient(CreateHttpMessageHandler(httpclientHandler)); + + return new HttpClientWrapper(client); + } + + 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; + }); + 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 29bb5c3e..1fdfef23 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -1,83 +1,84 @@ -using System; -using System.Collections.Concurrent; -using System.Net.Http; -using System.Threading.Tasks; -using Ocelot.Configuration; -using Ocelot.Logging; -using Ocelot.Responses; -using Polly.CircuitBreaker; -using Polly.Timeout; - -namespace Ocelot.Requester -{ - public class HttpClientHttpRequester : IHttpRequester - { - private readonly IHttpClientCache _cacheHandlers; - private readonly IOcelotLogger _logger; - - public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory, - IHttpClientCache cacheHandlers) - { - _logger = loggerFactory.CreateLogger(); - _cacheHandlers = cacheHandlers; - } - - public async Task> GetResponse(Request.Request request) - { - var builder = new HttpClientBuilder(); - - var cacheKey = GetCacheKey(request, builder); - - var httpClient = GetHttpClient(cacheKey, builder, request.UseCookieContainer, request.AllowAutoRedirect); - - 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)); - } - finally - { - _cacheHandlers.Set(cacheKey, httpClient, TimeSpan.FromHours(24)); - } - - } - - private IHttpClient GetHttpClient(string cacheKey, IHttpClientBuilder builder, bool useCookieContainer, bool allowAutoRedirect) - { - var httpClient = _cacheHandlers.Get(cacheKey); - - if (httpClient == null) - { - httpClient = builder.Create(useCookieContainer, allowAutoRedirect); - } - return httpClient; - } - - 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; - } - } -} +using System; +using System.Collections.Concurrent; +using System.Net.Http; +using System.Threading.Tasks; +using Ocelot.Configuration; +using Ocelot.Logging; +using Ocelot.Responses; +using Polly.CircuitBreaker; +using Polly.Timeout; + +namespace Ocelot.Requester +{ + public class HttpClientHttpRequester : IHttpRequester + { + private readonly IHttpClientCache _cacheHandlers; + private readonly IOcelotLogger _logger; + private readonly IServiceProvider _serviceProvider; + + public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory, IHttpClientCache cacheHandlers, IServiceProvider provider) + { + _logger = loggerFactory.CreateLogger(); + _cacheHandlers = cacheHandlers; + _serviceProvider = provider; + } + + public async Task> GetResponse(Request.Request request) + { + var builder = new HttpClientBuilder(); + + var cacheKey = GetCacheKey(request, builder); + + var httpClient = GetHttpClient(cacheKey, builder, request.UseCookieContainer, request.AllowAutoRedirect, request.IsTracing); + + 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)); + } + finally + { + _cacheHandlers.Set(cacheKey, httpClient, TimeSpan.FromHours(24)); + } + + } + + private IHttpClient GetHttpClient(string cacheKey, IHttpClientBuilder builder, bool useCookieContainer, bool allowAutoRedirect,bool isTracing) + { + var httpClient = _cacheHandlers.Get(cacheKey); + + if (httpClient == null) + { + httpClient = builder.Create(useCookieContainer, allowAutoRedirect, isTracing, _serviceProvider); + } + return httpClient; + } + + 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; + } + } +} diff --git a/src/Ocelot/Requester/IHttpClientBuilder.cs b/src/Ocelot/Requester/IHttpClientBuilder.cs index 6de5f87a..5ed30ef5 100644 --- a/src/Ocelot/Requester/IHttpClientBuilder.cs +++ b/src/Ocelot/Requester/IHttpClientBuilder.cs @@ -22,6 +22,6 @@ namespace Ocelot.Requester /// /// Defines if http client should use cookie container /// Defines if http client should allow auto redirect - IHttpClient Create(bool useCookies, bool allowAutoRedirect); + IHttpClient Create(bool useCookies, bool allowAutoRedirect, bool isTracing, IServiceProvider provider); } } diff --git a/src/Ocelot/Requester/IHttpRequester.cs b/src/Ocelot/Requester/IHttpRequester.cs index b2116818..81d9fa22 100644 --- a/src/Ocelot/Requester/IHttpRequester.cs +++ b/src/Ocelot/Requester/IHttpRequester.cs @@ -7,7 +7,5 @@ namespace Ocelot.Requester public interface IHttpRequester { Task> GetResponse(Request.Request request); - - } } diff --git a/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs b/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs index 412983de..ae3e7c5f 100644 --- a/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs +++ b/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs @@ -1,9 +1,8 @@ -using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; +using System.Threading.Tasks; namespace Ocelot.Requester.Middleware { @@ -16,7 +15,8 @@ namespace Ocelot.Requester.Middleware public HttpRequesterMiddleware(RequestDelegate next, IOcelotLoggerFactory loggerFactory, IHttpRequester requester, - IRequestScopedDataRepository requestScopedDataRepository) + IRequestScopedDataRepository requestScopedDataRepository +) :base(requestScopedDataRepository) { _next = next; @@ -25,7 +25,7 @@ namespace Ocelot.Requester.Middleware } public async Task Invoke(HttpContext context) - { + { var response = await _requester.GetResponse(Request); if (response.IsError) @@ -41,4 +41,4 @@ namespace Ocelot.Requester.Middleware SetHttpResponseMessageThisRequest(response.Data); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Requester/OcelotHttpTracingHandler.cs b/src/Ocelot/Requester/OcelotHttpTracingHandler.cs new file mode 100644 index 00000000..6435a113 --- /dev/null +++ b/src/Ocelot/Requester/OcelotHttpTracingHandler.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Butterfly.Client.Tracing; +using Butterfly.OpenTracing; + +namespace Ocelot.Requester +{ + public class OcelotHttpTracingHandler : DelegatingHandler + { + private readonly IServiceTracer _tracer; + private const string prefix_spanId = "ot-spanId"; + + public OcelotHttpTracingHandler(IServiceTracer tracer, HttpMessageHandler httpMessageHandler = null) + { + _tracer = tracer ?? throw new ArgumentNullException(nameof(tracer)); + InnerHandler = httpMessageHandler ?? new HttpClientHandler(); + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + return _tracer.ChildTraceAsync($"httpclient {request.Method}", DateTimeOffset.UtcNow, span => TracingSendAsync(span, request, cancellationToken)); + } + + protected virtual async Task TracingSendAsync(ISpan span, HttpRequestMessage request, CancellationToken cancellationToken) + { + IEnumerable traceIdVals = null; + if (request.Headers.TryGetValues(prefix_spanId, out traceIdVals)) + { + request.Headers.Remove(prefix_spanId); + request.Headers.TryAddWithoutValidation(prefix_spanId, span.SpanContext.SpanId); + }; + + span.Tags.Client().Component("HttpClient") + .HttpMethod(request.Method.Method) + .HttpUrl(request.RequestUri.OriginalString) + .HttpHost(request.RequestUri.Host) + .HttpPath(request.RequestUri.PathAndQuery) + .PeerAddress(request.RequestUri.OriginalString) + .PeerHostName(request.RequestUri.Host) + .PeerPort(request.RequestUri.Port); + + _tracer.Tracer.Inject(span.SpanContext, request.Headers, (c, k, v) => + { + if (!c.Contains(k)) + { + c.Add(k, v); + }; + }); + + span.Log(LogField.CreateNew().ClientSend()); + + var responseMessage = await base.SendAsync(request, cancellationToken); + + span.Log(LogField.CreateNew().ClientReceive()); + + return responseMessage; + } + } +} diff --git a/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs b/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs index b6ed2717..d0c12642 100644 --- a/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs +++ b/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs @@ -1,10 +1,10 @@ -using System.Collections.Generic; -using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Ocelot.Errors; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; +using System.Collections.Generic; +using System.Threading.Tasks; namespace Ocelot.Responder.Middleware { @@ -22,7 +22,8 @@ namespace Ocelot.Responder.Middleware IHttpResponder responder, IOcelotLoggerFactory loggerFactory, IRequestScopedDataRepository requestScopedDataRepository, - IErrorsToHttpStatusCodeMapper codeMapper) + IErrorsToHttpStatusCodeMapper codeMapper + ) :base(requestScopedDataRepository) { _next = next; @@ -33,14 +34,13 @@ namespace Ocelot.Responder.Middleware } public async Task Invoke(HttpContext context) - { + { await _next.Invoke(context); if (PipelineError) { var errors = PipelineErrors; _logger.LogError($"{PipelineErrors.Count} pipeline errors found in {MiddlewareName}. Setting error response status code"); - SetErrorResponse(context, errors); } else @@ -50,10 +50,10 @@ namespace Ocelot.Responder.Middleware } } - private void SetErrorResponse(HttpContext context, List errors) - { + private void SetErrorResponse(HttpContext context, List errors) + { var statusCode = _codeMapper.Map(errors); _responder.SetErrorResponseOnContext(context, statusCode); } } -} \ No newline at end of file +} diff --git a/test/Ocelot.IntegrationTests/RaftStartup.cs b/test/Ocelot.IntegrationTests/RaftStartup.cs index 70a91e2c..bb9f26d9 100644 --- a/test/Ocelot.IntegrationTests/RaftStartup.cs +++ b/test/Ocelot.IntegrationTests/RaftStartup.cs @@ -1,52 +1,53 @@ -using System; -using System.IO; -using System.Linq; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Ocelot.DependencyInjection; -using Ocelot.Middleware; -using Ocelot.Raft; -using Rafty.Concensus; -using Rafty.FiniteStateMachine; -using Rafty.Infrastructure; -using Rafty.Log; -using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; - -namespace Ocelot.IntegrationTests -{ - public class RaftStartup - { - public RaftStartup(IHostingEnvironment env) - { - var builder = new ConfigurationBuilder() - .SetBasePath(env.ContentRootPath) - .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) - .AddJsonFile("peers.json", optional: true, reloadOnChange: true) - .AddJsonFile("configuration.json") - .AddEnvironmentVariables(); - - Configuration = builder.Build(); - } - - public IConfiguration Configuration { get; } - - public virtual void ConfigureServices(IServiceCollection services) - { - services - .AddOcelot(Configuration) - .AddAdministration("/administration", "secret") - .AddRafty(); - } - - public virtual void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) - { - app.UseOcelot().Wait(); - } - } -} +using System; +using System.IO; +using System.Linq; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Ocelot.DependencyInjection; +using Ocelot.Middleware; +using Ocelot.Raft; +using Rafty.Concensus; +using Rafty.FiniteStateMachine; +using Rafty.Infrastructure; +using Rafty.Log; +using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; + +namespace Ocelot.IntegrationTests +{ + public class RaftStartup + { + public RaftStartup(IHostingEnvironment env) + { + var builder = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) + .AddJsonFile("peers.json", optional: true, reloadOnChange: true) + .AddJsonFile("configuration.json") + .AddEnvironmentVariables(); + + Configuration = builder.Build(); + } + + public IConfiguration Configuration { get; } + + public virtual void ConfigureServices(IServiceCollection services) + { + services + .AddOcelot(Configuration) + .AddAdministration("/administration", "secret") + .AddRafty() + ; + } + + public virtual void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + app.UseOcelot().Wait(); + } + } +} diff --git a/test/Ocelot.ManualTest/ManualTestStartup.cs b/test/Ocelot.ManualTest/ManualTestStartup.cs index 329984a1..0a39154d 100644 --- a/test/Ocelot.ManualTest/ManualTestStartup.cs +++ b/test/Ocelot.ManualTest/ManualTestStartup.cs @@ -1,40 +1,45 @@ -using System; -using CacheManager.Core; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Ocelot.DependencyInjection; -using Ocelot.Middleware; -using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; - -namespace Ocelot.ManualTest -{ - public class ManualTestStartup - { - public void ConfigureServices(IServiceCollection services) - { - Action settings = (x) => - { - x.WithDictionaryHandle(); - }; - - services.AddAuthentication() - .AddJwtBearer("TestKey", x => - { - x.Authority = "test"; - x.Audience = "test"; - }); - - services.AddOcelot() - .AddCacheManager(settings) - .AddAdministration("/administration", "secret"); - } - - public void Configure(IApplicationBuilder app) - { - app.UseOcelot().Wait(); - } - } -} +using System; +using CacheManager.Core; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Ocelot.DependencyInjection; +using Ocelot.Middleware; +using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; + +namespace Ocelot.ManualTest +{ + public class ManualTestStartup + { + public void ConfigureServices(IServiceCollection services) + { + Action settings = (x) => + { + x.WithDictionaryHandle(); + }; + + services.AddAuthentication() + .AddJwtBearer("TestKey", x => + { + x.Authority = "test"; + x.Audience = "test"; + }); + + services.AddOcelot() + .AddCacheManager(settings) + .AddOpenTracing(option => + { + option.CollectorUrl = "http://localhost:9618"; + option.Service = "Ocelot.ManualTest"; + }) + .AddAdministration("/administration", "secret"); + } + + public void Configure(IApplicationBuilder app) + { + app.UseOcelot().Wait(); + } + } +} diff --git a/test/Ocelot.ManualTest/configuration.json b/test/Ocelot.ManualTest/configuration.json index a8d01b24..7ec4eb69 100644 --- a/test/Ocelot.ManualTest/configuration.json +++ b/test/Ocelot.ManualTest/configuration.json @@ -1,287 +1,304 @@ -{ - "ReRoutes": [ - { - "DownstreamPathTemplate": "/", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 52876 - } - ], - "UpstreamPathTemplate": "/identityserverexample", - "UpstreamHttpMethod": [ "Get" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "AuthenticationOptions": { - "AuthenticationProviderKey": "TestKey", - "AllowedScopes": [ - "openid", - "offline_access" - ] - }, - "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", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 443 - } - ], - "UpstreamPathTemplate": "/posts", - "UpstreamHttpMethod": [ "Get" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/posts/{postId}", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": [ "Get" ], - "RequestIdKey": "ReRouteRequestId", - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/posts/{postId}/comments", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/posts/{postId}/comments", - "UpstreamHttpMethod": [ "Get" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/comments", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/comments", - "UpstreamHttpMethod": [ "Get" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/posts", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/posts", - "UpstreamHttpMethod": [ "Post" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/posts/{postId}", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": [ "Put" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/posts/{postId}", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": [ "Patch" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/posts/{postId}", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": [ "Delete" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/api/products", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/products", - "UpstreamHttpMethod": [ "Get" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/products/{productId}", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/products/{productId}", - "UpstreamHttpMethod": [ "Get" ], - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/products", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/products", - "UpstreamHttpMethod": [ "Post" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/api/products/{productId}", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/products/{productId}", - "UpstreamHttpMethod": [ "Put" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/posts", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/posts/", - "UpstreamHttpMethod": [ "Get" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "www.bbc.co.uk", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/bbc/", - "UpstreamHttpMethod": [ "Get" ] - } - ], - - "GlobalConfiguration": { - "RequestIdKey": "OcRequestId" - } -} +{ + "ReRoutes": [ + { + "DownstreamPathTemplate": "/api/values", + "DownstreamScheme": "http", + "UpstreamPathTemplate": "/api/values", + "UpstreamHttpMethod": [ "Get" ], + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5002 + } + ], + "HttpHandlerOptions": { + "AllowAutoRedirect": true, + "UseCookieContainer": true, + "UseTracing": true + } + }, + { + "DownstreamPathTemplate": "/", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 52876 + } + ], + "UpstreamPathTemplate": "/identityserverexample", + "UpstreamHttpMethod": [ "Get" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "AuthenticationOptions": { + "AuthenticationProviderKey": "TestKey", + "AllowedScopes": [ + "openid", + "offline_access" + ] + }, + "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", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 443 + } + ], + "UpstreamPathTemplate": "/posts", + "UpstreamHttpMethod": [ "Get" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Get" ], + "RequestIdKey": "ReRouteRequestId", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts/{postId}/comments", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/posts/{postId}/comments", + "UpstreamHttpMethod": [ "Get" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/comments", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/comments", + "UpstreamHttpMethod": [ "Get" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/posts", + "UpstreamHttpMethod": [ "Post" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Put" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Patch" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Delete" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/api/products", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/products", + "UpstreamHttpMethod": [ "Get" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/products/{productId}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/products/{productId}", + "UpstreamHttpMethod": [ "Get" ], + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/products", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/products", + "UpstreamHttpMethod": [ "Post" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/api/products/{productId}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/products/{productId}", + "UpstreamHttpMethod": [ "Put" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/posts", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/posts/", + "UpstreamHttpMethod": [ "Get" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "www.bbc.co.uk", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/bbc/", + "UpstreamHttpMethod": [ "Get" ] + } + ], + + "GlobalConfiguration": { + "RequestIdKey": "ot-traceid" + } +} diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index 22a73e2a..b2ff9a00 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -471,7 +471,7 @@ namespace Ocelot.UnitTests.Configuration { var reRouteOptions = new ReRouteOptionsBuilder() .Build(); - var httpHandlerOptions = new HttpHandlerOptions(true, true); + var httpHandlerOptions = new HttpHandlerOptions(true, true,false); this.Given(x => x.GivenTheConfigIs(new FileConfiguration { diff --git a/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs index 69f414e5..3821ef6b 100644 --- a/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs @@ -23,7 +23,7 @@ namespace Ocelot.UnitTests.Configuration public void should_create_options_with_useCookie_and_allowAutoRedirect_true_as_default() { var fileReRoute = new FileReRoute(); - var expectedOptions = new HttpHandlerOptions(true, true); + var expectedOptions = new HttpHandlerOptions(true, true, false); this.Given(x => GivenTheFollowing(fileReRoute)) .When(x => WhenICreateHttpHandlerOptions()) @@ -39,11 +39,12 @@ namespace Ocelot.UnitTests.Configuration HttpHandlerOptions = new FileHttpHandlerOptions { AllowAutoRedirect = false, - UseCookieContainer = false + UseCookieContainer = false, + UseTracing = false } }; - var expectedOptions = new HttpHandlerOptions(false, false); + var expectedOptions = new HttpHandlerOptions(false, false, false); this.Given(x => GivenTheFollowing(fileReRoute)) .When(x => WhenICreateHttpHandlerOptions()) @@ -66,6 +67,7 @@ namespace Ocelot.UnitTests.Configuration _httpHandlerOptions.ShouldNotBeNull(); _httpHandlerOptions.AllowAutoRedirect.ShouldBe(options.AllowAutoRedirect); _httpHandlerOptions.UseCookieContainer.ShouldBe(options.UseCookieContainer); + _httpHandlerOptions.UseTracing.ShouldBe(options.UseTracing); } } } diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index ef783591..b12f8d6d 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Text; -using CacheManager.Core; +using CacheManager.Core; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.Extensions.Configuration; @@ -13,8 +8,11 @@ using Ocelot.Configuration; using Ocelot.Configuration.File; using Ocelot.Configuration.Setter; using Ocelot.DependencyInjection; -using Ocelot.Logging; +using Ocelot.Requester; using Shouldly; +using System; +using System.Collections.Generic; +using System.Linq; using TestStack.BDDfy; using Xunit; @@ -96,6 +94,16 @@ namespace Ocelot.UnitTests.DependencyInjection .BDDfy(); } + [Fact] + public void should_set_up_tracing() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => WhenISetUpOpentracing()) + .When(x => WhenIAccessOcelotHttpTracingHandler()) + .BDDfy(); + } + + [Fact] public void should_set_up_without_passing_in_config() { @@ -193,6 +201,24 @@ namespace Ocelot.UnitTests.DependencyInjection } } + private void WhenISetUpOpentracing() + { + try + { + _ocelotBuilder.AddOpenTracing( + option => + { + option.CollectorUrl = "http://localhost:9618"; + option.Service = "Ocelot.ManualTest"; + } + ); + } + catch (Exception e) + { + _ex = e; + } + } + private void WhenIAccessLoggerFactory() { try @@ -205,6 +231,18 @@ namespace Ocelot.UnitTests.DependencyInjection } } + private void WhenIAccessOcelotHttpTracingHandler() + { + try + { + var tracingHandler = _serviceProvider.GetService(); + } + catch (Exception e) + { + _ex = e; + } + } + private void WhenIValidateScopes() { try diff --git a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs index 10cbc120..0027e90b 100644 --- a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs @@ -17,6 +17,7 @@ using Ocelot.Requester.QoS; using Ocelot.Configuration; using Microsoft.AspNetCore.Builder; + using Ocelot.Errors; public class HttpRequestBuilderMiddlewareTests : ServerHostedMiddlewareTest { @@ -51,18 +52,39 @@ new ReRouteBuilder() .WithRequestIdKey("LSRequestId") .WithUpstreamHttpMethod(new List { "Get" }) - .WithHttpHandlerOptions(new HttpHandlerOptions(true, true)) + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true,false)) .Build()); 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(), true, new NoQoSProvider(), false, false))) + .And(x => x.GivenTheRequestBuilderReturns(new Ocelot.Request.Request(new HttpRequestMessage(), true, new NoQoSProvider(), false, false,false))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) .BDDfy(); } + [Fact] + public void should_call_scoped_data_repository_QosProviderError() + { + + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithRequestIdKey("LSRequestId") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true)) + .Build()); + + this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) + .And(x => x.GivenTheQosProviderHouseReturns(new ErrorResponse(It.IsAny()))) + .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => x.GivenTheRequestBuilderReturns(new Ocelot.Request.Request(new HttpRequestMessage(), true, new NoQoSProvider(), false, false, false))) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheScopedDataRepositoryQosProviderError()) + .BDDfy(); + } + + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) { services.AddSingleton(); @@ -109,7 +131,9 @@ It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny())) + It.IsAny(), + It.IsAny() + )) .ReturnsAsync(_request); } @@ -118,5 +142,11 @@ _scopedRepository .Verify(x => x.Add("Request", _request.Data), Times.Once()); } + + private void ThenTheScopedDataRepositoryQosProviderError() + { + _scopedRepository + .Verify(x => x.Add("OcelotMiddlewareError", true), Times.Once()); + } } } diff --git a/test/Ocelot.UnitTests/Request/HttpRequestCreatorTests.cs b/test/Ocelot.UnitTests/Request/HttpRequestCreatorTests.cs index f4f67ca0..a7f2650a 100644 --- a/test/Ocelot.UnitTests/Request/HttpRequestCreatorTests.cs +++ b/test/Ocelot.UnitTests/Request/HttpRequestCreatorTests.cs @@ -1,79 +1,86 @@ -namespace Ocelot.UnitTests.Request -{ - using System.Net.Http; - - using Ocelot.Request.Builder; - using Ocelot.Requester.QoS; - using Ocelot.Responses; - using Shouldly; - using TestStack.BDDfy; - using Xunit; - - public class HttpRequestCreatorTests - { - private readonly IRequestCreator _requestCreator; - private readonly bool _isQos; - private readonly IQoSProvider _qoSProvider; - private readonly HttpRequestMessage _requestMessage; - private readonly bool _useCookieContainer; - private readonly bool _allowAutoRedirect; - - private Response _response; - - public HttpRequestCreatorTests() - { - _requestCreator = new HttpRequestCreator(); - _isQos = true; - _qoSProvider = new NoQoSProvider(); - _useCookieContainer = false; - _allowAutoRedirect = false; - - _requestMessage = new HttpRequestMessage(); - } - - [Fact] - public void ShouldBuildRequest() - { - this.When(x => x.WhenIBuildARequest()) - .Then(x => x.ThenTheRequestContainsTheRequestMessage()) - .Then(x => x.ThenTheRequestContainsTheIsQos()) - .Then(x => x.ThenTheRequestContainsTheQosProvider()) - .Then(x => x.ThenTheRequestContainsUseCookieContainer()) - .Then(x => x.ThenTheRequestContainsAllowAutoRedirect()) - .BDDfy(); - } - - private void WhenIBuildARequest() - { - _response = _requestCreator.Build(_requestMessage, - _isQos, _qoSProvider, _useCookieContainer, _allowAutoRedirect) - .GetAwaiter() - .GetResult(); - } - - private void ThenTheRequestContainsTheRequestMessage() - { - _response.Data.HttpRequestMessage.ShouldBe(_requestMessage); - } - - private void ThenTheRequestContainsTheIsQos() - { - _response.Data.IsQos.ShouldBe(_isQos); - } - - private void ThenTheRequestContainsTheQosProvider() - { - _response.Data.QosProvider.ShouldBe(_qoSProvider); - } - +namespace Ocelot.UnitTests.Request +{ + using System.Net.Http; + + using Ocelot.Request.Builder; + using Ocelot.Requester.QoS; + using Ocelot.Responses; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + + public class HttpRequestCreatorTests + { + private readonly IRequestCreator _requestCreator; + private readonly bool _isQos; + private readonly IQoSProvider _qoSProvider; + private readonly HttpRequestMessage _requestMessage; + private readonly bool _useCookieContainer; + private readonly bool _allowAutoRedirect; + private readonly bool _useTracing; + + private Response _response; + + public HttpRequestCreatorTests() + { + _requestCreator = new HttpRequestCreator(); + _isQos = true; + _qoSProvider = new NoQoSProvider(); + _useCookieContainer = false; + _allowAutoRedirect = false; + _useTracing = false; + _requestMessage = new HttpRequestMessage(); + } + + [Fact] + public void ShouldBuildRequest() + { + this.When(x => x.WhenIBuildARequest()) + .Then(x => x.ThenTheRequestContainsTheRequestMessage()) + .Then(x => x.ThenTheRequestContainsTheIsQos()) + .Then(x => x.ThenTheRequestContainsTheQosProvider()) + .Then(x => x.ThenTheRequestContainsUseCookieContainer()) + .Then(x => x.ThenTheRequestContainsUseTracing()) + .Then(x => x.ThenTheRequestContainsAllowAutoRedirect()) + .BDDfy(); + } + + private void WhenIBuildARequest() + { + _response = _requestCreator.Build(_requestMessage, + _isQos, _qoSProvider, _useCookieContainer, _allowAutoRedirect, _useTracing) + .GetAwaiter() + .GetResult(); + } + + private void ThenTheRequestContainsTheRequestMessage() + { + _response.Data.HttpRequestMessage.ShouldBe(_requestMessage); + } + + private void ThenTheRequestContainsTheIsQos() + { + _response.Data.IsQos.ShouldBe(_isQos); + } + + private void ThenTheRequestContainsTheQosProvider() + { + _response.Data.QosProvider.ShouldBe(_qoSProvider); + } + private void ThenTheRequestContainsUseCookieContainer() { _response.Data.UseCookieContainer.ShouldBe(_useCookieContainer); - } - + } + + private void ThenTheRequestContainsUseTracing() + { + _response.Data.IsTracing.ShouldBe(_useTracing); + } + private void ThenTheRequestContainsAllowAutoRedirect() { _response.Data.AllowAutoRedirect.ShouldBe(_allowAutoRedirect); - } - } -} + } + } +} diff --git a/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs new file mode 100644 index 00000000..2d649cad --- /dev/null +++ b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs @@ -0,0 +1,76 @@ +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Ocelot.Logging; +using Ocelot.Requester; +using Ocelot.Requester.QoS; +using Ocelot.Responses; +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Text; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Requester +{ + public class HttpClientHttpRequesterTest + { + private readonly Mock _cacheHandlers; + private Mock _serviceProvider; + private Response _response; + private readonly HttpClientHttpRequester _httpClientRequester; + private Ocelot.Request.Request _request; + private Mock _loggerFactory; + private Mock _logger; + + public HttpClientHttpRequesterTest() + { + _serviceProvider = new Mock(); + _logger = new Mock(); + _loggerFactory = new Mock(); + _loggerFactory + .Setup(x => x.CreateLogger()) + .Returns(_logger.Object); + _cacheHandlers = new Mock(); + _httpClientRequester = new HttpClientHttpRequester(_loggerFactory.Object, _cacheHandlers.Object, _serviceProvider.Object); + } + + [Fact] + public void should_call_request_correctly() + { + this.Given(x=>x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage() { RequestUri = new Uri("http://www.bbc.co.uk") }, false, new NoQoSProvider(), false, false, false))) + .When(x=>x.WhenIGetResponse()) + .Then(x => x.ThenTheResponseIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_call_request_UnableToCompleteRequest() + { + this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage() { RequestUri = new Uri("http://localhost:60080") }, false, new NoQoSProvider(), false, false, false))) + .When(x => x.WhenIGetResponse()) + .Then(x => x.ThenTheResponseIsCalledError()) + .BDDfy(); + } + + private void GivenTheRequestIs(Ocelot.Request.Request request) + { + _request = request; + } + + private void WhenIGetResponse() + { + _response = _httpClientRequester.GetResponse(_request).Result; + } + + private void ThenTheResponseIsCalledCorrectly() + { + Assert.True(_response.IsError == false); + } + + private void ThenTheResponseIsCalledError() + { + Assert.True(_response.IsError == true); + } + } +} diff --git a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs index 7c6bd487..8f51cb7d 100644 --- a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs @@ -1,81 +1,81 @@ -namespace Ocelot.UnitTests.Requester -{ - using System.Net.Http; - using Microsoft.AspNetCore.Builder; - using Microsoft.Extensions.DependencyInjection; - using Moq; - using Ocelot.Logging; - using Ocelot.Requester; - using Ocelot.Requester.Middleware; - using Ocelot.Requester.QoS; - using Ocelot.Responses; - using TestStack.BDDfy; - using Xunit; - - public class HttpRequesterMiddlewareTests : ServerHostedMiddlewareTest - { - private readonly Mock _requester; - private OkResponse _response; - private OkResponse _request; - - public HttpRequesterMiddlewareTests() - { - _requester = new Mock(); - - GivenTheTestServerIsConfigured(); - } - - [Fact] - public void should_call_scoped_data_repository_correctly() - { - this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage(),true, new NoQoSProvider(), false, false))) - .And(x => x.GivenTheRequesterReturns(new HttpResponseMessage())) - .And(x => x.GivenTheScopedRepoReturns()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheScopedRepoIsCalledCorrectly()) - .BDDfy(); - } - - protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - services.AddSingleton(); - services.AddLogging(); - services.AddSingleton(_requester.Object); - services.AddSingleton(ScopedRepository.Object); - } - - protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - app.UseHttpRequesterMiddleware(); - } - - private void GivenTheRequestIs(Ocelot.Request.Request request) - { - _request = new OkResponse(request); - ScopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_request); - } - - private void GivenTheRequesterReturns(HttpResponseMessage response) - { - _response = new OkResponse(response); - _requester - .Setup(x => x.GetResponse(It.IsAny())) - .ReturnsAsync(_response); - } - - private void GivenTheScopedRepoReturns() - { - ScopedRepository - .Setup(x => x.Add(It.IsAny(), _response.Data)) - .Returns(new OkResponse()); - } - - private void ThenTheScopedRepoIsCalledCorrectly() - { - ScopedRepository - .Verify(x => x.Add("HttpResponseMessage", _response.Data), Times.Once()); - } - } -} +namespace Ocelot.UnitTests.Requester +{ + using System.Net.Http; + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Logging; + using Ocelot.Requester; + using Ocelot.Requester.Middleware; + using Ocelot.Requester.QoS; + using Ocelot.Responses; + using TestStack.BDDfy; + using Xunit; + + public class HttpRequesterMiddlewareTests : ServerHostedMiddlewareTest + { + private readonly Mock _requester; + private OkResponse _response; + private OkResponse _request; + + public HttpRequesterMiddlewareTests() + { + _requester = new Mock(); + + GivenTheTestServerIsConfigured(); + } + + [Fact] + public void should_call_scoped_data_repository_correctly() + { + this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage(),true, new NoQoSProvider(), false, false,false))) + .And(x => x.GivenTheRequesterReturns(new HttpResponseMessage())) + .And(x => x.GivenTheScopedRepoReturns()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheScopedRepoIsCalledCorrectly()) + .BDDfy(); + } + + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(_requester.Object); + services.AddSingleton(ScopedRepository.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseHttpRequesterMiddleware(); + } + + private void GivenTheRequestIs(Ocelot.Request.Request request) + { + _request = new OkResponse(request); + ScopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(_request); + } + + private void GivenTheRequesterReturns(HttpResponseMessage response) + { + _response = new OkResponse(response); + _requester + .Setup(x => x.GetResponse(It.IsAny())) + .ReturnsAsync(_response); + } + + private void GivenTheScopedRepoReturns() + { + ScopedRepository + .Setup(x => x.Add(It.IsAny(), _response.Data)) + .Returns(new OkResponse()); + } + + private void ThenTheScopedRepoIsCalledCorrectly() + { + ScopedRepository + .Verify(x => x.Add("HttpResponseMessage", _response.Data), Times.Once()); + } + } +} diff --git a/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs b/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs index a8bf5475..ce98a972 100644 --- a/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs @@ -1,10 +1,14 @@ namespace Ocelot.UnitTests.Responder { + using System.Collections.Generic; using System.Net.Http; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Moq; + using Ocelot.DownstreamRouteFinder.Finder; + using Ocelot.Errors; using Ocelot.Logging; + using Ocelot.Requester; using Ocelot.Responder; using Ocelot.Responder.Middleware; using Ocelot.Responses; @@ -35,6 +39,17 @@ .BDDfy(); } + + [Fact] + public void should_return_any_errors() + { + this.Given(x => x.GivenTheHttpResponseMessageIs(new HttpResponseMessage())) + .And(x => x.GivenThereArePipelineErrors(new UnableToFindDownstreamRouteError())) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenThereAreNoErrors()) + .BDDfy(); + } + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) { services.AddSingleton(); @@ -68,5 +83,14 @@ { //todo a better assert? } + + private void GivenThereArePipelineErrors(Error error) + { + ScopedRepository + .Setup(x => x.Get("OcelotMiddlewareError")) + .Returns(new OkResponse(true)); + ScopedRepository.Setup(x => x.Get>("OcelotMiddlewareErrors")) + .Returns(new OkResponse>(new List() { error })); + } } }