Merge branch 'release-3.0.3'

This commit is contained in:
Tom Pallister 2018-02-13 14:29:55 +00:00
commit 065a013433
65 changed files with 2620 additions and 1183 deletions

View File

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.26730.15 VisualStudioVersion = 15.0.27130.2024
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5CFB79B7-C9DC-45A4-9A75-625D92471702}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5CFB79B7-C9DC-45A4-9A75-625D92471702}"
EndProject EndProject

View File

@ -158,14 +158,15 @@ if(-Not $SkipToolPackageRestore.IsPresent) {
if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or
($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) { ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) {
Write-Verbose -Message "Missing or changed package.config hash..." Write-Verbose -Message "Missing or changed package.config hash..."
Remove-Item * -Recurse -Exclude packages.config,nuget.exe Get-ChildItem -Exclude packages.config,nuget.exe,Cake.Bakery |
Remove-Item -Recurse
} }
Write-Verbose -Message "Restoring tools from NuGet..." Write-Verbose -Message "Restoring tools from NuGet..."
$NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`"" $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`""
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
Throw "An error occured while restoring NuGet tools." Throw "An error occurred while restoring NuGet tools."
} }
else else
{ {
@ -185,7 +186,7 @@ if (Test-Path $ADDINS_PACKAGES_CONFIG) {
$NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$ADDINS_DIR`"" $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$ADDINS_DIR`""
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
Throw "An error occured while restoring NuGet addins." Throw "An error occurred while restoring NuGet addins."
} }
Write-Verbose -Message ($NuGetOutput | out-string) Write-Verbose -Message ($NuGetOutput | out-string)
@ -202,7 +203,7 @@ if (Test-Path $MODULES_PACKAGES_CONFIG) {
$NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$MODULES_DIR`"" $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$MODULES_DIR`""
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
Throw "An error occured while restoring NuGet modules." Throw "An error occurred while restoring NuGet modules."
} }
Write-Verbose -Message ($NuGetOutput | out-string) Write-Verbose -Message ($NuGetOutput | out-string)

View File

@ -61,7 +61,8 @@ Here is an example ReRoute configuration, You don't need to set all of these thi
}, },
"HttpHandlerOptions": { "HttpHandlerOptions": {
"AllowAutoRedirect": true, "AllowAutoRedirect": true,
"UseCookieContainer": true "UseCookieContainer": true,
"UseTracing": true
}, },
"UseServiceDiscovery": false "UseServiceDiscovery": false
} }

View File

@ -0,0 +1,43 @@
Delegating Handers
==================
Ocelot allows the user to add delegating handlers to the HttpClient transport. This feature was requested `GitHub #208 <https://github.com/TomPallister/Ocelot/issues/208>`_ and I decided that it was going to be useful in various ways.
Usage
^^^^^^
In order to add delegating handlers to the HttpClient transport you need to do the following.
.. code-block:: csharp
services.AddOcelot()
.AddDelegatingHandler(() => new FakeHandler())
.AddDelegatingHandler(() => new FakeHandler());
Or for singleton like behaviour..
.. code-block:: csharp
var handlerOne = new FakeHandler();
var handlerTwo = new FakeHandler();
services.AddOcelot()
.AddDelegatingHandler(() => handlerOne)
.AddDelegatingHandler(() => handlerTwo);
You can have as many DelegatingHandlers as you want and they are run in a first in first out order. If you are using Ocelot's QoS functionality then that will always be run after your last delegating handler.
In order to create a class that can be used a delegating handler it must look as follows
.. code-block:: csharp
public class FakeHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
//do stuff and optionally call the base handler..
return await base.SendAsync(request, cancellationToken);
}
}
Hopefully other people will find this feature useful!

31
docs/features/tracing.rst Normal file
View File

@ -0,0 +1,31 @@
Tracing
=======
Ocelot providers tracing functionality from the excellent `Butterfly <https://github.com/ButterflyAPM>`_ project.
In order to use the tracing please read the Butterfly documentation.
In ocelot you need to do the following if you wish to trace a ReRoute.
In your ConfigureServices method
.. code-block:: csharp
services
.AddOcelot(Configuration)
.AddOpenTracing(option =>
{
//this is the url that the butterfly collector server is running on...
option.CollectorUrl = "http://localhost:9618";
option.Service = "Ocelot";
});
Then in your configuration.json add the following to the ReRoute you want to trace..
.. code-block:: json
"HttpHandlerOptions": {
"UseTracing": true
},
Ocelot will now send tracing information to Butterfly when this ReRoute is called.

View File

@ -30,9 +30,12 @@ Thanks for taking a look at the Ocelot documentation. Please use the left hand n
features/headerstransformation features/headerstransformation
features/claimstransformation features/claimstransformation
features/logging features/logging
features/tracing
features/requestid features/requestid
features/middlewareinjection features/middlewareinjection
features/loadbalancer features/loadbalancer
features/delegatinghandlers
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2

View File

@ -1,6 +1,6 @@
{ {
"projects": [ "src", "test" ], "projects": [ "src", "test" ],
"sdk": { "sdk": {
"version": "2.0.2" "version": "2.1.4"
} }
} }

View File

@ -7,7 +7,7 @@ namespace Ocelot.Configuration.Creator
public HttpHandlerOptions Create(FileReRoute fileReRoute) public HttpHandlerOptions Create(FileReRoute fileReRoute)
{ {
return new HttpHandlerOptions(fileReRoute.HttpHandlerOptions.AllowAutoRedirect, return new HttpHandlerOptions(fileReRoute.HttpHandlerOptions.AllowAutoRedirect,
fileReRoute.HttpHandlerOptions.UseCookieContainer); fileReRoute.HttpHandlerOptions.UseCookieContainer, fileReRoute.HttpHandlerOptions.UseTracing);
} }
} }
} }

View File

@ -11,5 +11,7 @@
public bool AllowAutoRedirect { get; set; } public bool AllowAutoRedirect { get; set; }
public bool UseCookieContainer { get; set; } public bool UseCookieContainer { get; set; }
public bool UseTracing { get; set; }
} }
} }

View File

@ -6,10 +6,11 @@
/// </summary> /// </summary>
public class HttpHandlerOptions public class HttpHandlerOptions
{ {
public HttpHandlerOptions(bool allowAutoRedirect, bool useCookieContainer) public HttpHandlerOptions(bool allowAutoRedirect, bool useCookieContainer, bool useTracing)
{ {
AllowAutoRedirect = allowAutoRedirect; AllowAutoRedirect = allowAutoRedirect;
UseCookieContainer = useCookieContainer; UseCookieContainer = useCookieContainer;
UseTracing = useTracing;
} }
/// <summary> /// <summary>
@ -21,5 +22,10 @@
/// Specify is handler has to use a cookie container /// Specify is handler has to use a cookie container
/// </summary> /// </summary>
public bool UseCookieContainer { get; private set; } public bool UseCookieContainer { get; private set; }
// <summary>
/// Specify is handler has to use a opentracing
/// </summary>
public bool UseTracing { get; private set; }
} }
} }

View File

@ -0,0 +1,7 @@
namespace Ocelot.DependencyInjection
{
public interface IOcelotAdministrationBuilder
{
IOcelotAdministrationBuilder AddRafty();
}
}

View File

@ -1,5 +1,7 @@
using Butterfly.Client.AspNetCore;
using CacheManager.Core; using CacheManager.Core;
using System; using System;
using System.Net.Http;
namespace Ocelot.DependencyInjection namespace Ocelot.DependencyInjection
{ {
@ -7,6 +9,8 @@ namespace Ocelot.DependencyInjection
{ {
IOcelotBuilder AddStoreOcelotConfigurationInConsul(); IOcelotBuilder AddStoreOcelotConfigurationInConsul();
IOcelotBuilder AddCacheManager(Action<ConfigurationBuilderCachePart> settings); IOcelotBuilder AddCacheManager(Action<ConfigurationBuilderCachePart> settings);
IOcelotBuilder AddOpenTracing(Action<ButterflyOptions> settings);
IOcelotAdministrationBuilder AddAdministration(string path, string secret); IOcelotAdministrationBuilder AddAdministration(string path, string secret);
IOcelotBuilder AddDelegatingHandler(Func<DelegatingHandler> delegatingHandler);
} }
} }

View File

@ -0,0 +1,34 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.Raft;
using Rafty.Concensus;
using Rafty.FiniteStateMachine;
using Rafty.Infrastructure;
using Rafty.Log;
namespace Ocelot.DependencyInjection
{
public class OcelotAdministrationBuilder : IOcelotAdministrationBuilder
{
private readonly IServiceCollection _services;
private readonly 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<ILog, SqlLiteLog>();
_services.AddSingleton<IFiniteStateMachine, OcelotFiniteStateMachine>();
_services.AddSingleton<ISettings>(settings);
_services.AddSingleton<IPeersProvider, FilePeersProvider>();
_services.AddSingleton<INode, Node>();
_services.Configure<FilePeers>(_configurationRoot);
return this;
}
}
}

View File

@ -35,7 +35,6 @@ using Ocelot.ServiceDiscovery;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
using System.Net.Http;
using System.Reflection; using System.Reflection;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using IdentityServer4.AccessTokenValidation; using IdentityServer4.AccessTokenValidation;
@ -46,19 +45,16 @@ using Ocelot.Configuration.Builder;
using FileConfigurationProvider = Ocelot.Configuration.Provider.FileConfigurationProvider; using FileConfigurationProvider = Ocelot.Configuration.Provider.FileConfigurationProvider;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
using System.Linq; using System.Linq;
using Ocelot.Raft; using System.Net.Http;
using Rafty.Concensus; using Butterfly.Client.AspNetCore;
using Rafty.FiniteStateMachine;
using Rafty.Infrastructure;
using Rafty.Log;
using Newtonsoft.Json;
namespace Ocelot.DependencyInjection namespace Ocelot.DependencyInjection
{ {
public class OcelotBuilder : IOcelotBuilder public class OcelotBuilder : IOcelotBuilder
{ {
private IServiceCollection _services; private readonly IServiceCollection _services;
private IConfiguration _configurationRoot; private readonly IConfiguration _configurationRoot;
private IDelegatingHandlerHandlerProvider _provider;
public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot) public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot)
{ {
@ -123,6 +119,9 @@ namespace Ocelot.DependencyInjection
_services.TryAddSingleton<IRequestMapper, RequestMapper>(); _services.TryAddSingleton<IRequestMapper, RequestMapper>();
_services.TryAddSingleton<IHttpHandlerOptionsCreator, HttpHandlerOptionsCreator>(); _services.TryAddSingleton<IHttpHandlerOptionsCreator, HttpHandlerOptionsCreator>();
_services.TryAddSingleton<IDownstreamAddressesCreator, DownstreamAddressesCreator>(); _services.TryAddSingleton<IDownstreamAddressesCreator, DownstreamAddressesCreator>();
_services.TryAddSingleton<IDelegatingHandlerHandlerProviderFactory, DelegatingHandlerHandlerProviderFactory>();
_services.TryAddSingleton<IDelegatingHandlerHandlerHouse, DelegatingHandlerHandlerHouse>();
// see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc // 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 // could maybe use a scoped data repository
_services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>(); _services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
@ -143,6 +142,11 @@ namespace Ocelot.DependencyInjection
_services.AddMiddlewareAnalysis(); _services.AddMiddlewareAnalysis();
_services.AddWebEncoders(); _services.AddWebEncoders();
_services.AddSingleton<IAdministrationPath>(new NullAdministrationPath()); _services.AddSingleton<IAdministrationPath>(new NullAdministrationPath());
//these get picked out later and added to http request
_provider = new DelegatingHandlerHandlerProvider();
_services.TryAddSingleton<IDelegatingHandlerHandlerProvider>(_provider);
_services.AddTransient<ITracingHandler, NoTracingHandler>();
} }
public IOcelotAdministrationBuilder AddAdministration(string path, string secret) public IOcelotAdministrationBuilder AddAdministration(string path, string secret)
@ -162,6 +166,19 @@ namespace Ocelot.DependencyInjection
return new OcelotAdministrationBuilder(_services, _configurationRoot); return new OcelotAdministrationBuilder(_services, _configurationRoot);
} }
public IOcelotBuilder AddDelegatingHandler(Func<DelegatingHandler> delegatingHandler)
{
_provider.Add(delegatingHandler);
return this;
}
public IOcelotBuilder AddOpenTracing(Action<ButterflyOptions> settings)
{
_services.AddTransient<ITracingHandler, OcelotHttpTracingHandler>();
_services.AddButterfly(settings);
return this;
}
public IOcelotBuilder AddStoreOcelotConfigurationInConsul() public IOcelotBuilder AddStoreOcelotConfigurationInConsul()
{ {
var serviceDiscoveryPort = _configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Port", 0); var serviceDiscoveryPort = _configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Port", 0);
@ -275,33 +292,4 @@ namespace Ocelot.DependencyInjection
}; };
} }
} }
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<ILog, SqlLiteLog>();
_services.AddSingleton<IFiniteStateMachine, OcelotFiniteStateMachine>();
_services.AddSingleton<ISettings>(settings);
_services.AddSingleton<IPeersProvider, FilePeersProvider>();
_services.AddSingleton<INode, Node>();
_services.Configure<FilePeers>(_configurationRoot);
return this;
}
}
} }

View File

@ -1,9 +1,6 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Configuration.Provider;
using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Errors; using Ocelot.Errors;
using Ocelot.Responses; using Ocelot.Responses;

View File

@ -62,7 +62,7 @@ namespace Ocelot.Errors.Middleware
private async Task TrySetGlobalRequestId(HttpContext context) private async Task TrySetGlobalRequestId(HttpContext context)
{ {
//try and get the global request id and set it for logs... //try and get the global request id and set it for logs...
//shoudl this basically be immutable per request...i guess it should! //should this basically be immutable per request...i guess it should!
//first thing is get config //first thing is get config
var configuration = await _configProvider.Get(); var configuration = await _configProvider.Get();

View File

@ -33,6 +33,7 @@
UnmappableRequestError, UnmappableRequestError,
RateLimitOptionsError, RateLimitOptionsError,
PathTemplateDoesntStartWithForwardSlash, PathTemplateDoesntStartWithForwardSlash,
FileValidationFailedError FileValidationFailedError,
UnableToFindDelegatingHandlerProviderError
} }
} }

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.Provider;
using Ocelot.Infrastructure.RequestData; using Ocelot.Infrastructure.RequestData;
using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Logging; using Ocelot.Logging;
@ -46,11 +45,14 @@ namespace Ocelot.LoadBalancer.Middleware
} }
var uriBuilder = new UriBuilder(DownstreamRequest.RequestUri); var uriBuilder = new UriBuilder(DownstreamRequest.RequestUri);
uriBuilder.Host = hostAndPort.Data.DownstreamHost; uriBuilder.Host = hostAndPort.Data.DownstreamHost;
if (hostAndPort.Data.DownstreamPort > 0) if (hostAndPort.Data.DownstreamPort > 0)
{ {
uriBuilder.Port = hostAndPort.Data.DownstreamPort; uriBuilder.Port = hostAndPort.Data.DownstreamPort;
} }
DownstreamRequest.RequestUri = uriBuilder.Uri; DownstreamRequest.RequestUri = uriBuilder.Uri;
try try

View File

@ -1,6 +1,8 @@
using System; using System;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DiagnosticAdapter; using Microsoft.Extensions.DiagnosticAdapter;
using Butterfly.Client.AspNetCore;
using Butterfly.OpenTracing;
namespace Ocelot.Logging namespace Ocelot.Logging
{ {
@ -17,6 +19,7 @@ namespace Ocelot.Logging
public virtual void OnMiddlewareStarting(HttpContext httpContext, string name) public virtual void OnMiddlewareStarting(HttpContext httpContext, string name)
{ {
_logger.LogTrace($"MiddlewareStarting: {name}; {httpContext.Request.Path}"); _logger.LogTrace($"MiddlewareStarting: {name}; {httpContext.Request.Path}");
Event(httpContext, $"MiddlewareStarting: {name}; {httpContext.Request.Path}");
} }
[DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareException")] [DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareException")]
@ -29,6 +32,13 @@ namespace Ocelot.Logging
public virtual void OnMiddlewareFinished(HttpContext httpContext, string name) public virtual void OnMiddlewareFinished(HttpContext httpContext, string name)
{ {
_logger.LogTrace($"MiddlewareFinished: {name}; {httpContext.Response.StatusCode}"); _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));
} }
} }
} }

View File

@ -23,24 +23,25 @@
<DebugSymbols>True</DebugSymbols> <DebugSymbols>True</DebugSymbols>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentValidation" Version="7.2.1"/> <PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.5" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.1.0"/> <PackageReference Include="FluentValidation" Version="7.2.1" />
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0"/> <PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.1.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0"/> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.0"/> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0"/> <PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.DiagnosticAdapter" Version="2.0.0"/> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0"/> <PackageReference Include="Microsoft.Extensions.DiagnosticAdapter" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.0"/> <PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.0"/> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.0"/> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.0" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.0"/> <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.0" />
<PackageReference Include="CacheManager.Core" Version="1.1.1"/> <PackageReference Include="System.Text.RegularExpressions" Version="4.3.0" />
<PackageReference Include="CacheManager.Microsoft.Extensions.Configuration" Version="1.1.1"/> <PackageReference Include="CacheManager.Core" Version="1.1.1" />
<PackageReference Include="CacheManager.Microsoft.Extensions.Logging" Version="1.1.1"/> <PackageReference Include="CacheManager.Microsoft.Extensions.Configuration" Version="1.1.1" />
<PackageReference Include="Consul" Version="0.7.2.3"/> <PackageReference Include="CacheManager.Microsoft.Extensions.Logging" Version="1.1.1" />
<PackageReference Include="Polly" Version="5.3.1"/> <PackageReference Include="Consul" Version="0.7.2.3" />
<PackageReference Include="IdentityServer4" Version="2.0.2"/> <PackageReference Include="Polly" Version="5.3.1" />
<PackageReference Include="Rafty" Version="0.4.2"/> <PackageReference Include="IdentityServer4" Version="2.0.2" />
<PackageReference Include="Rafty" Version="0.4.2" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -12,9 +12,11 @@ namespace Ocelot.Request.Builder
bool isQos, bool isQos,
IQoSProvider qosProvider, IQoSProvider qosProvider,
bool useCookieContainer, bool useCookieContainer,
bool allowAutoRedirect) bool allowAutoRedirect,
string reRouteKey,
bool isTracing)
{ {
return new OkResponse<Request>(new Request(httpRequestMessage, isQos, qosProvider, allowAutoRedirect, useCookieContainer)); return new OkResponse<Request>(new Request(httpRequestMessage, isQos, qosProvider, allowAutoRedirect, useCookieContainer, reRouteKey, isTracing));
} }
} }
} }

View File

@ -13,6 +13,8 @@
bool isQos, bool isQos,
IQoSProvider qosProvider, IQoSProvider qosProvider,
bool useCookieContainer, bool useCookieContainer,
bool allowAutoRedirect); bool allowAutoRedirect,
string reRouteKe,
bool isTracing);
} }
} }

View File

@ -46,16 +46,17 @@ namespace Ocelot.Request.Middleware
DownstreamRoute.ReRoute.IsQos, DownstreamRoute.ReRoute.IsQos,
qosProvider.Data, qosProvider.Data,
DownstreamRoute.ReRoute.HttpHandlerOptions.UseCookieContainer, DownstreamRoute.ReRoute.HttpHandlerOptions.UseCookieContainer,
DownstreamRoute.ReRoute.HttpHandlerOptions.AllowAutoRedirect); DownstreamRoute.ReRoute.HttpHandlerOptions.AllowAutoRedirect,
DownstreamRoute.ReRoute.ReRouteKey,
DownstreamRoute.ReRoute.HttpHandlerOptions.UseTracing);
if (buildResult.IsError) if (buildResult.IsError)
{ {
_logger.LogDebug("IRequestCreator returned an error, setting pipeline error"); _logger.LogDebug("IRequestCreator returned an error, setting pipeline error");
SetPipelineError(buildResult.Errors); SetPipelineError(buildResult.Errors);
return; return;
} }
_logger.LogDebug("setting upstream request"); _logger.LogDebug("setting upstream request");
SetUpstreamRequestForThisRequest(buildResult.Data); SetUpstreamRequestForThisRequest(buildResult.Data);

View File

@ -10,19 +10,26 @@ namespace Ocelot.Request
bool isQos, bool isQos,
IQoSProvider qosProvider, IQoSProvider qosProvider,
bool allowAutoRedirect, bool allowAutoRedirect,
bool useCookieContainer) bool useCookieContainer,
string reRouteKey,
bool isTracing
)
{ {
HttpRequestMessage = httpRequestMessage; HttpRequestMessage = httpRequestMessage;
IsQos = isQos; IsQos = isQos;
QosProvider = qosProvider; QosProvider = qosProvider;
AllowAutoRedirect = allowAutoRedirect; AllowAutoRedirect = allowAutoRedirect;
UseCookieContainer = useCookieContainer; UseCookieContainer = useCookieContainer;
ReRouteKey = reRouteKey;
IsTracing = isTracing;
} }
public HttpRequestMessage HttpRequestMessage { get; private set; } public HttpRequestMessage HttpRequestMessage { get; private set; }
public bool IsQos { get; private set; } public bool IsQos { get; private set; }
public bool IsTracing { get; private set; }
public IQoSProvider QosProvider { get; private set; } public IQoSProvider QosProvider { get; private set; }
public bool AllowAutoRedirect { get; private set; } public bool AllowAutoRedirect { get; private set; }
public bool UseCookieContainer { get; private set; } public bool UseCookieContainer { get; private set; }
public string ReRouteKey { get; private set; }
} }
} }

View File

@ -0,0 +1,49 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using Ocelot.Errors;
using Ocelot.Responses;
namespace Ocelot.Requester
{
public class DelegatingHandlerHandlerHouse : IDelegatingHandlerHandlerHouse
{
private readonly IDelegatingHandlerHandlerProviderFactory _factory;
private readonly ConcurrentDictionary<string, IDelegatingHandlerHandlerProvider> _housed;
public DelegatingHandlerHandlerHouse(IDelegatingHandlerHandlerProviderFactory factory)
{
_factory = factory;
_housed = new ConcurrentDictionary<string, IDelegatingHandlerHandlerProvider>();
}
public Response<IDelegatingHandlerHandlerProvider> Get(Request.Request request)
{
try
{
if (_housed.TryGetValue(request.ReRouteKey, out var provider))
{
//todo once day we might need a check here to see if we need to create a new provider
provider = _housed[request.ReRouteKey];
return new OkResponse<IDelegatingHandlerHandlerProvider>(provider);
}
provider = _factory.Get(request);
AddHoused(request.ReRouteKey, provider);
return new OkResponse<IDelegatingHandlerHandlerProvider>(provider);
}
catch (Exception ex)
{
return new ErrorResponse<IDelegatingHandlerHandlerProvider>(new List<Error>()
{
new UnableToFindDelegatingHandlerProviderError($"unabe to find delegating handler provider for {request.ReRouteKey} exception is {ex}")
});
}
}
private void AddHoused(string key, IDelegatingHandlerHandlerProvider provider)
{
_housed.AddOrUpdate(key, provider, (k, v) => provider);
}
}
}

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
namespace Ocelot.Requester
{
public class DelegatingHandlerHandlerProvider : IDelegatingHandlerHandlerProvider
{
private readonly Dictionary<int, Func<DelegatingHandler>> _handlers;
public DelegatingHandlerHandlerProvider()
{
_handlers = new Dictionary<int, Func<DelegatingHandler>>();
}
public void Add(Func<DelegatingHandler> handler)
{
var key = _handlers.Count == 0 ? 0 : _handlers.Count + 1;
_handlers[key] = handler;
}
public List<Func<DelegatingHandler>> Get()
{
return _handlers.Count > 0 ? _handlers.OrderBy(x => x.Key).Select(x => x.Value).ToList() : new List<Func<DelegatingHandler>>();
}
}
}

View File

@ -0,0 +1,43 @@
using System.Net.Http;
using Ocelot.Logging;
namespace Ocelot.Requester
{
public class DelegatingHandlerHandlerProviderFactory : IDelegatingHandlerHandlerProviderFactory
{
private readonly ITracingHandler _tracingHandler;
private readonly IOcelotLoggerFactory _loggerFactory;
private readonly IDelegatingHandlerHandlerProvider _allRoutesProvider;
public DelegatingHandlerHandlerProviderFactory(IOcelotLoggerFactory loggerFactory, IDelegatingHandlerHandlerProvider allRoutesProvider, ITracingHandler tracingHandler)
{
_tracingHandler = tracingHandler;
_loggerFactory = loggerFactory;
_allRoutesProvider = allRoutesProvider;
}
public IDelegatingHandlerHandlerProvider Get(Request.Request request)
{
var handlersAppliedToAll = _allRoutesProvider.Get();
var provider = new DelegatingHandlerHandlerProvider();
foreach (var handler in handlersAppliedToAll)
{
provider.Add(handler);
}
if (request.IsTracing)
{
provider.Add(() => (DelegatingHandler)_tracingHandler);
}
if (request.IsQos)
{
provider.Add(() => new PollyCircuitBreakingDelegatingHandler(request.QosProvider, _loggerFactory));
}
return provider;
}
}
}

View File

@ -1,39 +1,33 @@
using System; using System.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks;
using Ocelot.Logging;
using Ocelot.Requester.QoS;
namespace Ocelot.Requester namespace Ocelot.Requester
{ {
internal class HttpClientBuilder : IHttpClientBuilder public class HttpClientBuilder : IHttpClientBuilder
{ {
private readonly Dictionary<int, Func<DelegatingHandler>> _handlers = new Dictionary<int, Func<DelegatingHandler>>(); private readonly IDelegatingHandlerHandlerHouse _house;
public IHttpClientBuilder WithQos(IQoSProvider qosProvider, IOcelotLogger logger) public HttpClientBuilder(IDelegatingHandlerHandlerHouse house)
{ {
_handlers.Add(5000, () => new PollyCircuitBreakingDelegatingHandler(qosProvider, logger)); _house = house;
return this;
} }
public IHttpClient Create(bool useCookies, bool allowAutoRedirect) public IHttpClient Create(Request.Request request)
{ {
var httpclientHandler = new HttpClientHandler { AllowAutoRedirect = allowAutoRedirect, UseCookies = useCookies}; var httpclientHandler = new HttpClientHandler { AllowAutoRedirect = request.AllowAutoRedirect, UseCookies = request.UseCookieContainer};
var client = new HttpClient(CreateHttpMessageHandler(httpclientHandler)); var client = new HttpClient(CreateHttpMessageHandler(httpclientHandler, request));
return new HttpClientWrapper(client); return new HttpClientWrapper(client);
} }
private HttpMessageHandler CreateHttpMessageHandler(HttpMessageHandler httpMessageHandler) private HttpMessageHandler CreateHttpMessageHandler(HttpMessageHandler httpMessageHandler, Request.Request request)
{ {
_handlers var provider = _house.Get(request);
.OrderByDescending(handler => handler.Key)
.Select(handler => handler.Value) //todo handle error
provider.Data.Get()
.Select(handler => handler)
.Reverse() .Reverse()
.ToList() .ToList()
.ForEach(handler => .ForEach(handler =>
@ -45,22 +39,4 @@ namespace Ocelot.Requester
return httpMessageHandler; return httpMessageHandler;
} }
} }
/// <summary>
/// This class was made to make unit testing easier when HttpClient is used.
/// </summary>
internal class HttpClientWrapper : IHttpClient
{
public HttpClient Client { get; }
public HttpClientWrapper(HttpClient client)
{
Client = client;
}
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
{
return Client.SendAsync(request);
}
}
} }

View File

@ -1,8 +1,6 @@
using System; using System;
using System.Collections.Concurrent;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Configuration;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Responses; using Ocelot.Responses;
using Polly.CircuitBreaker; using Polly.CircuitBreaker;
@ -14,21 +12,24 @@ namespace Ocelot.Requester
{ {
private readonly IHttpClientCache _cacheHandlers; private readonly IHttpClientCache _cacheHandlers;
private readonly IOcelotLogger _logger; private readonly IOcelotLogger _logger;
private readonly IDelegatingHandlerHandlerHouse _house;
public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory, public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory,
IHttpClientCache cacheHandlers) IHttpClientCache cacheHandlers,
IDelegatingHandlerHandlerHouse house)
{ {
_logger = loggerFactory.CreateLogger<HttpClientHttpRequester>(); _logger = loggerFactory.CreateLogger<HttpClientHttpRequester>();
_cacheHandlers = cacheHandlers; _cacheHandlers = cacheHandlers;
_house = house;
} }
public async Task<Response<HttpResponseMessage>> GetResponse(Request.Request request) public async Task<Response<HttpResponseMessage>> GetResponse(Request.Request request)
{ {
var builder = new HttpClientBuilder(); var builder = new HttpClientBuilder(_house);
var cacheKey = GetCacheKey(request, builder); var cacheKey = GetCacheKey(request);
var httpClient = GetHttpClient(cacheKey, builder, request.UseCookieContainer, request.AllowAutoRedirect); var httpClient = GetHttpClient(cacheKey, builder, request);
try try
{ {
@ -56,24 +57,24 @@ namespace Ocelot.Requester
} }
private IHttpClient GetHttpClient(string cacheKey, IHttpClientBuilder builder, bool useCookieContainer, bool allowAutoRedirect) private IHttpClient GetHttpClient(string cacheKey, IHttpClientBuilder builder, Request.Request request)
{ {
var httpClient = _cacheHandlers.Get(cacheKey); var httpClient = _cacheHandlers.Get(cacheKey);
if (httpClient == null) if (httpClient == null)
{ {
httpClient = builder.Create(useCookieContainer, allowAutoRedirect); httpClient = builder.Create(request);
} }
return httpClient; return httpClient;
} }
private string GetCacheKey(Request.Request request, IHttpClientBuilder builder) private string GetCacheKey(Request.Request request)
{ {
string baseUrl = $"{request.HttpRequestMessage.RequestUri.Scheme}://{request.HttpRequestMessage.RequestUri.Authority}"; var baseUrl = $"{request.HttpRequestMessage.RequestUri.Scheme}://{request.HttpRequestMessage.RequestUri.Authority}";
if (request.IsQos) if (request.IsQos)
{ {
builder.WithQos(request.QosProvider, _logger);
baseUrl = $"{baseUrl}{request.QosProvider.CircuitBreaker.CircuitBreakerPolicy.PolicyKey}"; baseUrl = $"{baseUrl}{request.QosProvider.CircuitBreaker.CircuitBreakerPolicy.PolicyKey}";
} }

View File

@ -0,0 +1,23 @@
using System.Net.Http;
using System.Threading.Tasks;
namespace Ocelot.Requester
{
/// <summary>
/// This class was made to make unit testing easier when HttpClient is used.
/// </summary>
internal class HttpClientWrapper : IHttpClient
{
public HttpClient Client { get; }
public HttpClientWrapper(HttpClient client)
{
Client = client;
}
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
{
return Client.SendAsync(request);
}
}
}

View File

@ -0,0 +1,9 @@
using Ocelot.Responses;
namespace Ocelot.Requester
{
public interface IDelegatingHandlerHandlerHouse
{
Response<IDelegatingHandlerHandlerProvider> Get(Request.Request request);
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
namespace Ocelot.Requester
{
public interface IDelegatingHandlerHandlerProvider
{
void Add(Func<DelegatingHandler> handler);
List<Func<DelegatingHandler>> Get();
}
}

View File

@ -0,0 +1,7 @@
namespace Ocelot.Requester
{
public interface IDelegatingHandlerHandlerProviderFactory
{
IDelegatingHandlerHandlerProvider Get(Request.Request request);
}
}

View File

@ -1,27 +1,14 @@
using System; using System.Net.Http;
using System.Collections.Generic; using Ocelot.Configuration;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Ocelot.Logging;
using Ocelot.Requester.QoS;
using System.Net;
namespace Ocelot.Requester namespace Ocelot.Requester
{ {
public interface IHttpClientBuilder public interface IHttpClientBuilder
{ {
/// <summary>
/// Sets a PollyCircuitBreakingDelegatingHandler .
/// </summary>
IHttpClientBuilder WithQos(IQoSProvider qosProvider, IOcelotLogger logger);
/// <summary> /// <summary>
/// Creates the <see cref="HttpClient"/> /// Creates the <see cref="HttpClient"/>
/// </summary> /// </summary>
/// <param name="useCookies">Defines if http client should use cookie container</param> /// <param name="request"></param>
/// <param name="allowAutoRedirect">Defines if http client should allow auto redirect</param> IHttpClient Create(Request.Request request);
IHttpClient Create(bool useCookies, bool allowAutoRedirect);
} }
} }

View File

@ -7,7 +7,5 @@ namespace Ocelot.Requester
public interface IHttpRequester public interface IHttpRequester
{ {
Task<Response<HttpResponseMessage>> GetResponse(Request.Request request); Task<Response<HttpResponseMessage>> GetResponse(Request.Request request);
} }
} }

View File

@ -1,9 +1,8 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Ocelot.Infrastructure.RequestData; using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware; using Ocelot.Middleware;
using System.Threading.Tasks;
namespace Ocelot.Requester.Middleware namespace Ocelot.Requester.Middleware
{ {
@ -16,7 +15,8 @@ namespace Ocelot.Requester.Middleware
public HttpRequesterMiddleware(RequestDelegate next, public HttpRequesterMiddleware(RequestDelegate next,
IOcelotLoggerFactory loggerFactory, IOcelotLoggerFactory loggerFactory,
IHttpRequester requester, IHttpRequester requester,
IRequestScopedDataRepository requestScopedDataRepository) IRequestScopedDataRepository requestScopedDataRepository
)
:base(requestScopedDataRepository) :base(requestScopedDataRepository)
{ {
_next = next; _next = next;

View File

@ -0,0 +1,71 @@
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 interface ITracingHandler
{
}
public class NoTracingHandler : DelegatingHandler, ITracingHandler
{
}
public class OcelotHttpTracingHandler : DelegatingHandler, ITracingHandler
{
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<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return _tracer.ChildTraceAsync($"httpclient {request.Method}", DateTimeOffset.UtcNow, span => TracingSendAsync(span, request, cancellationToken));
}
protected virtual async Task<HttpResponseMessage> TracingSendAsync(ISpan span, HttpRequestMessage request, CancellationToken cancellationToken)
{
IEnumerable<string> 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;
}
}
}

View File

@ -16,10 +16,10 @@ namespace Ocelot.Requester
public PollyCircuitBreakingDelegatingHandler( public PollyCircuitBreakingDelegatingHandler(
IQoSProvider qoSProvider, IQoSProvider qoSProvider,
IOcelotLogger logger) IOcelotLoggerFactory loggerFactory)
{ {
_qoSProvider = qoSProvider; _qoSProvider = qoSProvider;
_logger = logger; _logger = loggerFactory.CreateLogger<PollyCircuitBreakingDelegatingHandler>();
} }
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)

View File

@ -0,0 +1,12 @@
using Ocelot.Errors;
namespace Ocelot.Requester
{
public class UnableToFindDelegatingHandlerProviderError : Error
{
public UnableToFindDelegatingHandlerProviderError(string message)
: base(message, OcelotErrorCode.UnableToFindDelegatingHandlerProviderError)
{
}
}
}

View File

@ -1,10 +1,10 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Errors; using Ocelot.Errors;
using Ocelot.Infrastructure.RequestData; using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware; using Ocelot.Middleware;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Ocelot.Responder.Middleware namespace Ocelot.Responder.Middleware
{ {
@ -22,7 +22,8 @@ namespace Ocelot.Responder.Middleware
IHttpResponder responder, IHttpResponder responder,
IOcelotLoggerFactory loggerFactory, IOcelotLoggerFactory loggerFactory,
IRequestScopedDataRepository requestScopedDataRepository, IRequestScopedDataRepository requestScopedDataRepository,
IErrorsToHttpStatusCodeMapper codeMapper) IErrorsToHttpStatusCodeMapper codeMapper
)
:base(requestScopedDataRepository) :base(requestScopedDataRepository)
{ {
_next = next; _next = next;
@ -40,7 +41,6 @@ namespace Ocelot.Responder.Middleware
{ {
var errors = PipelineErrors; var errors = PipelineErrors;
_logger.LogError($"{PipelineErrors.Count} pipeline errors found in {MiddlewareName}. Setting error response status code"); _logger.LogError($"{PipelineErrors.Count} pipeline errors found in {MiddlewareName}. Setting error response status code");
SetErrorResponse(context, errors); SetErrorResponse(context, errors);
} }
else else

View File

@ -4,6 +4,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Consul; using Consul;
using Ocelot.Infrastructure.Extensions; using Ocelot.Infrastructure.Extensions;
using Ocelot.Logging;
using Ocelot.Values; using Ocelot.Values;
namespace Ocelot.ServiceDiscovery namespace Ocelot.ServiceDiscovery
@ -11,13 +12,18 @@ namespace Ocelot.ServiceDiscovery
public class ConsulServiceDiscoveryProvider : IServiceDiscoveryProvider public class ConsulServiceDiscoveryProvider : IServiceDiscoveryProvider
{ {
private readonly ConsulRegistryConfiguration _consulConfig; private readonly ConsulRegistryConfiguration _consulConfig;
private readonly IOcelotLogger _logger;
private readonly ConsulClient _consul; private readonly ConsulClient _consul;
private const string VersionPrefix = "version-"; private const string VersionPrefix = "version-";
public ConsulServiceDiscoveryProvider(ConsulRegistryConfiguration consulRegistryConfiguration) public ConsulServiceDiscoveryProvider(ConsulRegistryConfiguration consulRegistryConfiguration, IOcelotLoggerFactory factory)
{ {;
_logger = factory.CreateLogger<ConsulServiceDiscoveryProvider>();
var consulHost = string.IsNullOrEmpty(consulRegistryConfiguration?.HostName) ? "localhost" : consulRegistryConfiguration.HostName; var consulHost = string.IsNullOrEmpty(consulRegistryConfiguration?.HostName) ? "localhost" : consulRegistryConfiguration.HostName;
var consulPort = consulRegistryConfiguration?.Port ?? 8500; var consulPort = consulRegistryConfiguration?.Port ?? 8500;
_consulConfig = new ConsulRegistryConfiguration(consulHost, consulPort, consulRegistryConfiguration?.KeyOfServiceInConsul); _consulConfig = new ConsulRegistryConfiguration(consulHost, consulPort, consulRegistryConfiguration?.KeyOfServiceInConsul);
_consul = new ConsulClient(config => _consul = new ConsulClient(config =>
@ -30,7 +36,19 @@ namespace Ocelot.ServiceDiscovery
{ {
var queryResult = await _consul.Health.Service(_consulConfig.KeyOfServiceInConsul, string.Empty, true); var queryResult = await _consul.Health.Service(_consulConfig.KeyOfServiceInConsul, string.Empty, true);
var services = queryResult.Response.Select(BuildService); var services = new List<Service>();
foreach (var serviceEntry in queryResult.Response)
{
if (IsValid(serviceEntry))
{
services.Add(BuildService(serviceEntry));
}
else
{
_logger.LogError($"Unable to use service Address: {serviceEntry.Service.Address} and Port: {serviceEntry.Service.Port} as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0");
}
}
return services.ToList(); return services.ToList();
} }
@ -45,6 +63,16 @@ namespace Ocelot.ServiceDiscovery
serviceEntry.Service.Tags ?? Enumerable.Empty<string>()); serviceEntry.Service.Tags ?? Enumerable.Empty<string>());
} }
private bool IsValid(ServiceEntry serviceEntry)
{
if (serviceEntry.Service.Address.Contains("http://") || serviceEntry.Service.Address.Contains("https://") || serviceEntry.Service.Port <= 0)
{
return false;
}
return true;
}
private string GetVersionFromStrings(IEnumerable<string> strings) private string GetVersionFromStrings(IEnumerable<string> strings)
{ {
return strings return strings

View File

@ -1,11 +1,19 @@
using System.Collections.Generic; using System.Collections.Generic;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Logging;
using Ocelot.Values; using Ocelot.Values;
namespace Ocelot.ServiceDiscovery namespace Ocelot.ServiceDiscovery
{ {
public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory
{ {
private readonly IOcelotLoggerFactory _factory;
public ServiceDiscoveryProviderFactory(IOcelotLoggerFactory factory)
{
_factory = factory;
}
public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, ReRoute reRoute) public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, ReRoute reRoute)
{ {
if (reRoute.UseServiceDiscovery) if (reRoute.UseServiceDiscovery)
@ -28,7 +36,7 @@ namespace Ocelot.ServiceDiscovery
private IServiceDiscoveryProvider GetServiceDiscoveryProvider(string keyOfServiceInConsul, string providerHostName, int providerPort) private IServiceDiscoveryProvider GetServiceDiscoveryProvider(string keyOfServiceInConsul, string providerHostName, int providerPort)
{ {
var consulRegistryConfiguration = new ConsulRegistryConfiguration(providerHostName, providerPort, keyOfServiceInConsul); var consulRegistryConfiguration = new ConsulRegistryConfiguration(providerHostName, providerPort, keyOfServiceInConsul);
return new ConsulServiceDiscoveryProvider(consulRegistryConfiguration); return new ConsulServiceDiscoveryProvider(consulRegistryConfiguration, _factory);
} }
} }
} }

View File

@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.AcceptanceTests
{
public class HttpDelegatingHandlersTests
{
private IWebHost _builder;
private readonly Steps _steps;
private string _downstreamPath;
public HttpDelegatingHandlersTests()
{
_steps = new Steps();
}
[Fact]
public void should_call_handlers()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 61879,
}
},
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
}
}
};
var handlerOne = new FakeHandler();
var handlerTwo = new FakeHandler();
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:61879", "/", 200, "Hello from Laura"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningWithHandlers(handlerOne, handlerTwo))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.And(x => ThenTheHandlersAreCalledCorrectly(handlerOne, handlerTwo))
.BDDfy();
}
private void ThenTheHandlersAreCalledCorrectly(FakeHandler one, FakeHandler two)
{
one.TimeCalled.ShouldBeLessThan(two.TimeCalled);
}
class FakeHandler : DelegatingHandler
{
public DateTime TimeCalled { get; private set; }
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
TimeCalled = DateTime.Now;
return await base.SendAsync(request, cancellationToken);
}
}
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody)
{
_builder = new WebHostBuilder()
.UseUrls(baseUrl)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.Configure(app =>
{
app.UsePathBase(basePath);
app.Run(async context =>
{
_downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value;
if (_downstreamPath != basePath)
{
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync("downstream path didnt match base path");
}
else
{
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(responseBody);
}
});
})
.Build();
_builder.Start();
}
}
}

View File

@ -88,6 +88,38 @@ namespace Ocelot.AcceptanceTests
_ocelotClient = _ocelotServer.CreateClient(); _ocelotClient = _ocelotServer.CreateClient();
} }
/// <summary>
/// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step.
/// </summary>
public void GivenOcelotIsRunningWithHandlers(DelegatingHandler handlerOne, DelegatingHandler handlerTwo)
{
_webHostBuilder = new WebHostBuilder();
_webHostBuilder.ConfigureServices(s =>
{
s.AddSingleton(_webHostBuilder);
s.AddOcelot()
.AddDelegatingHandler(() => handlerOne)
.AddDelegatingHandler(() => handlerTwo);
});
_webHostBuilder.ConfigureAppConfiguration((hostingContext, config) =>
{
config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
config.AddJsonFile("configuration.json");
config.AddEnvironmentVariables();
}).Configure(a =>
{
a.UseOcelot().Wait();
});
_ocelotServer = new TestServer(_webHostBuilder);
_ocelotClient = _ocelotServer.CreateClient();
}
/// <summary> /// <summary>
/// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step. /// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step.
/// </summary> /// </summary>

View File

@ -41,7 +41,8 @@ namespace Ocelot.IntegrationTests
services services
.AddOcelot(Configuration) .AddOcelot(Configuration)
.AddAdministration("/administration", "secret") .AddAdministration("/administration", "secret")
.AddRafty(); .AddRafty()
;
} }
public virtual void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) public virtual void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)

View File

@ -1,13 +1,7 @@
using System;
using CacheManager.Core;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Ocelot.DependencyInjection; using Ocelot.DependencyInjection;
using Ocelot.Middleware; using Ocelot.Middleware;
using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder;
namespace Ocelot.ManualTest namespace Ocelot.ManualTest
{ {
@ -15,11 +9,6 @@ namespace Ocelot.ManualTest
{ {
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
Action<ConfigurationBuilderCachePart> settings = (x) =>
{
x.WithDictionaryHandle();
};
services.AddAuthentication() services.AddAuthentication()
.AddJwtBearer("TestKey", x => .AddJwtBearer("TestKey", x =>
{ {
@ -28,7 +17,15 @@ namespace Ocelot.ManualTest
}); });
services.AddOcelot() services.AddOcelot()
.AddCacheManager(settings) .AddCacheManager(x =>
{
x.WithDictionaryHandle();
})
.AddOpenTracing(option =>
{
option.CollectorUrl = "http://localhost:9618";
option.Service = "Ocelot.ManualTest";
})
.AddAdministration("/administration", "secret"); .AddAdministration("/administration", "secret");
} }

View File

@ -1,5 +1,22 @@
{ {
"ReRoutes": [ "ReRoutes": [
{
"DownstreamPathTemplate": "/api/values",
"DownstreamScheme": "http",
"UpstreamPathTemplate": "/api/values",
"UpstreamHttpMethod": [ "Get" ],
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5001
}
],
"HttpHandlerOptions": {
"AllowAutoRedirect": true,
"UseCookieContainer": true,
"UseTracing": true
}
},
{ {
"DownstreamPathTemplate": "/", "DownstreamPathTemplate": "/",
"DownstreamScheme": "http", "DownstreamScheme": "http",
@ -57,6 +74,11 @@
], ],
"UpstreamPathTemplate": "/posts", "UpstreamPathTemplate": "/posts",
"UpstreamHttpMethod": [ "Get" ], "UpstreamHttpMethod": [ "Get" ],
"HttpHandlerOptions": {
"AllowAutoRedirect": true,
"UseCookieContainer": true,
"UseTracing": true
},
"QoSOptions": { "QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, "ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10, "DurationOfBreak": 10,
@ -75,6 +97,11 @@
"UpstreamPathTemplate": "/posts/{postId}", "UpstreamPathTemplate": "/posts/{postId}",
"UpstreamHttpMethod": [ "Get" ], "UpstreamHttpMethod": [ "Get" ],
"RequestIdKey": "ReRouteRequestId", "RequestIdKey": "ReRouteRequestId",
"HttpHandlerOptions": {
"AllowAutoRedirect": true,
"UseCookieContainer": true,
"UseTracing": true
},
"QoSOptions": { "QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, "ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10, "DurationOfBreak": 10,
@ -92,6 +119,11 @@
], ],
"UpstreamPathTemplate": "/posts/{postId}/comments", "UpstreamPathTemplate": "/posts/{postId}/comments",
"UpstreamHttpMethod": [ "Get" ], "UpstreamHttpMethod": [ "Get" ],
"HttpHandlerOptions": {
"AllowAutoRedirect": true,
"UseCookieContainer": true,
"UseTracing": true
},
"QoSOptions": { "QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, "ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10, "DurationOfBreak": 10,
@ -282,6 +314,6 @@
], ],
"GlobalConfiguration": { "GlobalConfiguration": {
"RequestIdKey": "OcRequestId" "RequestIdKey": "ot-traceid"
} }
} }

View File

@ -471,7 +471,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
var reRouteOptions = new ReRouteOptionsBuilder() var reRouteOptions = new ReRouteOptionsBuilder()
.Build(); .Build();
var httpHandlerOptions = new HttpHandlerOptions(true, true); var httpHandlerOptions = new HttpHandlerOptions(true, true,false);
this.Given(x => x.GivenTheConfigIs(new FileConfiguration this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{ {

View File

@ -23,7 +23,7 @@ namespace Ocelot.UnitTests.Configuration
public void should_create_options_with_useCookie_and_allowAutoRedirect_true_as_default() public void should_create_options_with_useCookie_and_allowAutoRedirect_true_as_default()
{ {
var fileReRoute = new FileReRoute(); var fileReRoute = new FileReRoute();
var expectedOptions = new HttpHandlerOptions(true, true); var expectedOptions = new HttpHandlerOptions(true, true, false);
this.Given(x => GivenTheFollowing(fileReRoute)) this.Given(x => GivenTheFollowing(fileReRoute))
.When(x => WhenICreateHttpHandlerOptions()) .When(x => WhenICreateHttpHandlerOptions())
@ -39,11 +39,12 @@ namespace Ocelot.UnitTests.Configuration
HttpHandlerOptions = new FileHttpHandlerOptions HttpHandlerOptions = new FileHttpHandlerOptions
{ {
AllowAutoRedirect = false, 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)) this.Given(x => GivenTheFollowing(fileReRoute))
.When(x => WhenICreateHttpHandlerOptions()) .When(x => WhenICreateHttpHandlerOptions())
@ -66,6 +67,7 @@ namespace Ocelot.UnitTests.Configuration
_httpHandlerOptions.ShouldNotBeNull(); _httpHandlerOptions.ShouldNotBeNull();
_httpHandlerOptions.AllowAutoRedirect.ShouldBe(options.AllowAutoRedirect); _httpHandlerOptions.AllowAutoRedirect.ShouldBe(options.AllowAutoRedirect);
_httpHandlerOptions.UseCookieContainer.ShouldBe(options.UseCookieContainer); _httpHandlerOptions.UseCookieContainer.ShouldBe(options.UseCookieContainer);
_httpHandlerOptions.UseTracing.ShouldBe(options.UseTracing);
} }
} }
} }

View File

@ -1,8 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text;
using CacheManager.Core; using CacheManager.Core;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.AspNetCore.Hosting.Internal;
@ -13,8 +12,10 @@ using Ocelot.Configuration;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.Configuration.Setter; using Ocelot.Configuration.Setter;
using Ocelot.DependencyInjection; using Ocelot.DependencyInjection;
using Ocelot.Logging; using Ocelot.Requester;
using Ocelot.UnitTests.Requester;
using Shouldly; using Shouldly;
using System;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
@ -22,11 +23,11 @@ namespace Ocelot.UnitTests.DependencyInjection
{ {
public class OcelotBuilderTests public class OcelotBuilderTests
{ {
private IServiceCollection _services; private readonly IServiceCollection _services;
private IServiceProvider _serviceProvider; private IServiceProvider _serviceProvider;
private IConfiguration _configRoot; private readonly IConfiguration _configRoot;
private IOcelotBuilder _ocelotBuilder; private IOcelotBuilder _ocelotBuilder;
private int _maxRetries; private readonly int _maxRetries;
public OcelotBuilderTests() public OcelotBuilderTests()
{ {
@ -40,6 +41,19 @@ namespace Ocelot.UnitTests.DependencyInjection
} }
private Exception _ex; private Exception _ex;
[Fact]
public void should_add_delegating_handlers()
{
var fakeOne = new FakeDelegatingHandler(0);
var fakeTwo = new FakeDelegatingHandler(1);
this.Given(x => WhenISetUpOcelotServices())
.When(x => AddDelegate(fakeOne))
.And(x => AddDelegate(fakeTwo))
.Then(x => ThenTheProviderIsRegisteredAndReturnsHandlers())
.BDDfy();
}
[Fact] [Fact]
public void should_set_up_services() public void should_set_up_services()
{ {
@ -76,7 +90,7 @@ namespace Ocelot.UnitTests.DependencyInjection
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_set_up_rafty() public void should_set_up_rafty()
{ {
this.Given(x => WhenISetUpOcelotServices()) this.Given(x => WhenISetUpOcelotServices())
@ -96,6 +110,16 @@ namespace Ocelot.UnitTests.DependencyInjection
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_set_up_tracing()
{
this.Given(x => WhenISetUpOcelotServices())
.When(x => WhenISetUpOpentracing())
.When(x => WhenIAccessOcelotHttpTracingHandler())
.BDDfy();
}
[Fact] [Fact]
public void should_set_up_without_passing_in_config() public void should_set_up_without_passing_in_config()
{ {
@ -111,6 +135,17 @@ namespace Ocelot.UnitTests.DependencyInjection
path.Path.ShouldBe("/administration"); path.Path.ShouldBe("/administration");
} }
private void ThenTheProviderIsRegisteredAndReturnsHandlers()
{
_serviceProvider = _services.BuildServiceProvider();
var provider = _serviceProvider.GetService<IDelegatingHandlerHandlerProvider>();
var handlers = provider.Get();
var handler = (FakeDelegatingHandler)handlers[0].Invoke();
handler.Order.ShouldBe(0);
handler = (FakeDelegatingHandler)handlers[1].Invoke();
handler.Order.ShouldBe(1);
}
private void OnlyOneVersionOfEachCacheIsRegistered() private void OnlyOneVersionOfEachCacheIsRegistered()
{ {
var outputCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache<CachedResponse>)); var outputCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache<CachedResponse>));
@ -149,6 +184,11 @@ namespace Ocelot.UnitTests.DependencyInjection
} }
} }
private void AddDelegate(DelegatingHandler handler)
{
_ocelotBuilder.AddDelegatingHandler(() => handler);
}
private void ThenAnOcelotBuilderIsReturned() private void ThenAnOcelotBuilderIsReturned()
{ {
_ocelotBuilder.ShouldBeOfType<OcelotBuilder>(); _ocelotBuilder.ShouldBeOfType<OcelotBuilder>();
@ -193,6 +233,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() private void WhenIAccessLoggerFactory()
{ {
try try
@ -205,6 +263,18 @@ namespace Ocelot.UnitTests.DependencyInjection
} }
} }
private void WhenIAccessOcelotHttpTracingHandler()
{
try
{
var tracingHandler = _serviceProvider.GetService<OcelotHttpTracingHandler>();
}
catch (Exception e)
{
_ex = e;
}
}
private void WhenIValidateScopes() private void WhenIValidateScopes()
{ {
try try

View File

@ -17,6 +17,7 @@
using Ocelot.Requester.QoS; using Ocelot.Requester.QoS;
using Ocelot.Configuration; using Ocelot.Configuration;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Ocelot.Errors;
public class HttpRequestBuilderMiddlewareTests : ServerHostedMiddlewareTest public class HttpRequestBuilderMiddlewareTests : ServerHostedMiddlewareTest
{ {
@ -51,18 +52,39 @@
new ReRouteBuilder() new ReRouteBuilder()
.WithRequestIdKey("LSRequestId") .WithRequestIdKey("LSRequestId")
.WithUpstreamHttpMethod(new List<string> { "Get" }) .WithUpstreamHttpMethod(new List<string> { "Get" })
.WithHttpHandlerOptions(new HttpHandlerOptions(true, true)) .WithHttpHandlerOptions(new HttpHandlerOptions(true, true,false))
.Build()); .Build());
this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) this.Given(x => x.GivenTheDownStreamUrlIs("any old string"))
.And(x => x.GivenTheQosProviderHouseReturns(new OkResponse<IQoSProvider>(new NoQoSProvider()))) .And(x => x.GivenTheQosProviderHouseReturns(new OkResponse<IQoSProvider>(new NoQoSProvider())))
.And(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) .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()) .When(x => x.WhenICallTheMiddleware())
.Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly())
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_call_scoped_data_repository_QosProviderError()
{
var downstreamRoute = new DownstreamRoute(new List<PlaceholderNameAndValue>(),
new ReRouteBuilder()
.WithRequestIdKey("LSRequestId")
.WithUpstreamHttpMethod(new List<string> { "Get" })
.WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true))
.Build());
this.Given(x => x.GivenTheDownStreamUrlIs("any old string"))
.And(x => x.GivenTheQosProviderHouseReturns(new ErrorResponse<IQoSProvider>(It.IsAny<Error>())))
.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) protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services)
{ {
services.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>(); services.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
@ -109,6 +131,8 @@
It.IsAny<bool>(), It.IsAny<bool>(),
It.IsAny<IQoSProvider>(), It.IsAny<IQoSProvider>(),
It.IsAny<bool>(), It.IsAny<bool>(),
It.IsAny<bool>(),
It.IsAny<string>(),
It.IsAny<bool>())) It.IsAny<bool>()))
.ReturnsAsync(_request); .ReturnsAsync(_request);
} }
@ -118,5 +142,11 @@
_scopedRepository _scopedRepository
.Verify(x => x.Add("Request", _request.Data), Times.Once()); .Verify(x => x.Add("Request", _request.Data), Times.Once());
} }
private void ThenTheScopedDataRepositoryQosProviderError()
{
_scopedRepository
.Verify(x => x.Add("OcelotMiddlewareError", true), Times.Once());
}
} }
} }

View File

@ -1,4 +1,4 @@
namespace Ocelot.UnitTests.Request namespace Ocelot.UnitTests.Request
{ {
using System.Net.Http; using System.Net.Http;
@ -17,8 +17,9 @@
private readonly HttpRequestMessage _requestMessage; private readonly HttpRequestMessage _requestMessage;
private readonly bool _useCookieContainer; private readonly bool _useCookieContainer;
private readonly bool _allowAutoRedirect; private readonly bool _allowAutoRedirect;
private Response<Ocelot.Request.Request> _response; private Response<Ocelot.Request.Request> _response;
private string _reRouteKey;
private readonly bool _useTracing;
public HttpRequestCreatorTests() public HttpRequestCreatorTests()
{ {
@ -39,6 +40,7 @@
.Then(x => x.ThenTheRequestContainsTheIsQos()) .Then(x => x.ThenTheRequestContainsTheIsQos())
.Then(x => x.ThenTheRequestContainsTheQosProvider()) .Then(x => x.ThenTheRequestContainsTheQosProvider())
.Then(x => x.ThenTheRequestContainsUseCookieContainer()) .Then(x => x.ThenTheRequestContainsUseCookieContainer())
.Then(x => x.ThenTheRequestContainsUseTracing())
.Then(x => x.ThenTheRequestContainsAllowAutoRedirect()) .Then(x => x.ThenTheRequestContainsAllowAutoRedirect())
.BDDfy(); .BDDfy();
} }
@ -46,7 +48,7 @@
private void WhenIBuildARequest() private void WhenIBuildARequest()
{ {
_response = _requestCreator.Build(_requestMessage, _response = _requestCreator.Build(_requestMessage,
_isQos, _qoSProvider, _useCookieContainer, _allowAutoRedirect) _isQos, _qoSProvider, _useCookieContainer, _allowAutoRedirect, _reRouteKey, _useTracing)
.GetAwaiter() .GetAwaiter()
.GetResult(); .GetResult();
} }
@ -71,6 +73,11 @@
_response.Data.UseCookieContainer.ShouldBe(_useCookieContainer); _response.Data.UseCookieContainer.ShouldBe(_useCookieContainer);
} }
private void ThenTheRequestContainsUseTracing()
{
_response.Data.IsTracing.ShouldBe(_useTracing);
}
private void ThenTheRequestContainsAllowAutoRedirect() private void ThenTheRequestContainsAllowAutoRedirect()
{ {
_response.Data.AllowAutoRedirect.ShouldBe(_allowAutoRedirect); _response.Data.AllowAutoRedirect.ShouldBe(_allowAutoRedirect);

View File

@ -0,0 +1,108 @@
using System;
using System.Net.Http;
using Moq;
using Ocelot.Requester;
using Ocelot.Responses;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Requester
{
public class DelegatingHandlerHandlerHouseTests
{
private readonly DelegatingHandlerHandlerHouse _house;
private Mock<IDelegatingHandlerHandlerProviderFactory> _factory;
private readonly Mock<IDelegatingHandlerHandlerProvider> _provider;
private Ocelot.Request.Request _request;
private Response<IDelegatingHandlerHandlerProvider> _result;
public DelegatingHandlerHandlerHouseTests()
{
_provider = new Mock<IDelegatingHandlerHandlerProvider>();
_factory = new Mock<IDelegatingHandlerHandlerProviderFactory>();
_house = new DelegatingHandlerHandlerHouse(_factory.Object);
}
[Fact]
public void should_create_and_store_provider()
{
var request = new Ocelot.Request.Request(new HttpRequestMessage(), true, null, true, true, "key", false);
this.Given(x => GivenTheRequest(request))
.And(x => GivenTheProviderReturns())
.When(x => WhenIGet())
.Then(x => ThenTheFactoryIsCalled(1))
.And(x => ThenTheProviderIsNotNull())
.BDDfy();
}
[Fact]
public void should_get_provider()
{
var request = new Ocelot.Request.Request(new HttpRequestMessage(), true, null, true, true, "key", false);
this.Given(x => GivenTheRequest(request))
.And(x => GivenTheProviderReturns())
.And(x => WhenIGet())
.And(x => GivenTheFactoryIsCleared())
.When(x => WhenIGet())
.Then(x => ThenTheFactoryIsCalled(0))
.And(x => ThenTheProviderIsNotNull())
.BDDfy();
}
[Fact]
public void should_return_error()
{
var request = new Ocelot.Request.Request(new HttpRequestMessage(), true, null, true, true, "key", false);
this.Given(x => GivenTheRequest(request))
.And(x => GivenTheProviderThrows())
.When(x => WhenIGet())
.And(x => ThenAnErrorIsReturned())
.BDDfy();
}
private void ThenAnErrorIsReturned()
{
_result.IsError.ShouldBeTrue();
_result.Errors[0].ShouldBeOfType<UnableToFindDelegatingHandlerProviderError>();
}
private void GivenTheProviderThrows()
{
_factory.Setup(x => x.Get(It.IsAny<Ocelot.Request.Request>())).Throws<Exception>();
}
private void GivenTheFactoryIsCleared()
{
_factory = new Mock<IDelegatingHandlerHandlerProviderFactory>();
}
private void ThenTheProviderIsNotNull()
{
_result.Data.ShouldBe(_provider.Object);
}
private void WhenIGet()
{
_result = _house.Get(_request);
}
private void GivenTheRequest(Ocelot.Request.Request request)
{
_request = request;
}
private void GivenTheProviderReturns()
{
_factory.Setup(x => x.Get(It.IsAny<Ocelot.Request.Request>())).Returns(_provider.Object);
}
private void ThenTheFactoryIsCalled(int times)
{
_factory.Verify(x => x.Get(_request), Times.Exactly(times));
}
}
}

View File

@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using Moq;
using Ocelot.Logging;
using Ocelot.Requester;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Requester
{
public class DelegatingHandlerHandlerProviderFactoryTests
{
private readonly DelegatingHandlerHandlerProviderFactory _factory;
private Mock<IOcelotLoggerFactory> _loggerFactory;
private Ocelot.Request.Request _request;
private IDelegatingHandlerHandlerProvider _provider;
private readonly Mock<IDelegatingHandlerHandlerProvider> _allRoutesProvider;
public DelegatingHandlerHandlerProviderFactoryTests()
{
_allRoutesProvider = new Mock<IDelegatingHandlerHandlerProvider>();
_loggerFactory = new Mock<IOcelotLoggerFactory>();
_factory = new DelegatingHandlerHandlerProviderFactory(_loggerFactory.Object, _allRoutesProvider.Object, null);
}
[Fact]
public void should_all_from_all_routes_provider_and_qos()
{
var handlers = new List<Func<DelegatingHandler>>
{
() => new FakeDelegatingHandler(0),
() => new FakeDelegatingHandler(1)
};
var request = new Ocelot.Request.Request(new HttpRequestMessage(), true, null, true, true, "", false);
this.Given(x => GivenTheFollowingRequest(request))
.And(x => GivenTheAllRoutesProviderReturns(handlers))
.When(x => WhenIGet())
.Then(x => ThenThereIsDelegatesInProvider(3))
.And(x => ThenTheDelegatesAreAddedCorrectly())
.And(x => ThenItIsPolly(2))
.BDDfy();
}
[Fact]
public void should_return_provider_with_no_delegates()
{
var request = new Ocelot.Request.Request(new HttpRequestMessage(), false, null, true, true, "", false);
this.Given(x => GivenTheFollowingRequest(request))
.And(x => GivenTheAllRoutesProviderReturns())
.When(x => WhenIGet())
.Then(x => ThenNoDelegatesAreInTheProvider())
.BDDfy();
}
[Fact]
public void should_return_provider_with_qos_delegate()
{
var request = new Ocelot.Request.Request(new HttpRequestMessage(), true, null, true, true, "", false);
this.Given(x => GivenTheFollowingRequest(request))
.And(x => GivenTheAllRoutesProviderReturns())
.When(x => WhenIGet())
.Then(x => ThenThereIsDelegatesInProvider(1))
.And(x => ThenItIsPolly(0))
.BDDfy();
}
private void ThenTheDelegatesAreAddedCorrectly()
{
var delegates = _provider.Get();
var del = delegates[0].Invoke();
var handler = (FakeDelegatingHandler) del;
handler.Order.ShouldBe(0);
del = delegates[1].Invoke();
handler = (FakeDelegatingHandler)del;
handler.Order.ShouldBe(1);
}
private void GivenTheAllRoutesProviderReturns()
{
_allRoutesProvider.Setup(x => x.Get()).Returns(new List<Func<DelegatingHandler>>());
}
private void GivenTheAllRoutesProviderReturns(List<Func<DelegatingHandler>> handlers)
{
_allRoutesProvider.Setup(x => x.Get()).Returns(handlers);
}
private void ThenItIsPolly(int i)
{
var delegates = _provider.Get();
var del = delegates[i].Invoke();
del.ShouldBeOfType<PollyCircuitBreakingDelegatingHandler>();
}
private void ThenThereIsDelegatesInProvider(int count)
{
_provider.ShouldNotBeNull();
_provider.Get().Count.ShouldBe(count);
}
private void GivenTheFollowingRequest(Ocelot.Request.Request request)
{
_request = request;
}
private void WhenIGet()
{
_provider = _factory.Get(_request);
}
private void ThenNoDelegatesAreInTheProvider()
{
_provider.ShouldNotBeNull();
_provider.Get().Count.ShouldBe(0);
}
}
}

View File

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using Ocelot.Requester;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Requester
{
public class DelegatingHandlerHandlerProviderTests
{
private readonly DelegatingHandlerHandlerProvider _provider;
private List<Func<DelegatingHandler>> _handlers;
public DelegatingHandlerHandlerProviderTests()
{
_provider = new DelegatingHandlerHandlerProvider();
}
[Fact]
public void should_return_empty_list()
{
this.When(x => WhenIGet())
.Then(x => ThenAnEmptyListIsReturned())
.BDDfy();
}
[Fact]
public void should_get_delegating_handlers_in_order_first_in_first_out()
{
this.Given(x => GivenTheHandlers())
.When(x => WhenIGet())
.Then(x => ThenTheHandlersAreReturnedInOrder())
.BDDfy();
}
private void ThenAnEmptyListIsReturned()
{
_handlers.Count.ShouldBe(0);
}
private void ThenTheHandlersAreReturnedInOrder()
{
var handler = (FakeDelegatingHandler)_handlers[0].Invoke();
handler.Order.ShouldBe(0);
handler = (FakeDelegatingHandler)_handlers[1].Invoke();
handler.Order.ShouldBe(1);
}
private void WhenIGet()
{
_handlers = _provider.Get();
}
private void GivenTheHandlers()
{
_provider.Add(() => new FakeDelegatingHandler(0));
_provider.Add(() => new FakeDelegatingHandler(1));
}
}
}

View File

@ -0,0 +1,28 @@
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace Ocelot.UnitTests.Requester
{
public class FakeDelegatingHandler : DelegatingHandler
{
public FakeDelegatingHandler()
{
}
public FakeDelegatingHandler(int order)
{
Order = order;
}
public int Order {get;private set;}
public DateTime TimeCalled {get;private set;}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
TimeCalled = DateTime.Now;
return new HttpResponseMessage();
}
}
}

View File

@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using Moq;
using Ocelot.Requester;
using Ocelot.Responses;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Requester
{
public class HttpClientBuilderTests
{
private readonly HttpClientBuilder _builder;
private readonly Mock<IDelegatingHandlerHandlerHouse> _house;
private readonly Mock<IDelegatingHandlerHandlerProvider> _provider;
private IHttpClientBuilder _builderResult;
private IHttpClient _httpClient;
private HttpResponseMessage _response;
private Ocelot.Request.Request _request;
public HttpClientBuilderTests()
{
_provider = new Mock<IDelegatingHandlerHandlerProvider>();
_house = new Mock<IDelegatingHandlerHandlerHouse>();
_builder = new HttpClientBuilder(_house.Object);
}
[Fact]
public void should_build_http_client()
{
this.Given(x => GivenTheProviderReturns())
.And(x => GivenARequest())
.And(x => GivenTheHouseReturns())
.When(x => WhenIBuild())
.Then(x => ThenTheHttpClientShouldNotBeNull())
.BDDfy();
}
[Fact]
public void should_call_delegating_handlers_in_order()
{
var fakeOne = new FakeDelegatingHandler();
var fakeTwo = new FakeDelegatingHandler();
var handlers = new List<Func<DelegatingHandler>>()
{
() => fakeOne,
() => fakeTwo
};
this.Given(x => GivenTheProviderReturns(handlers))
.And(x => GivenARequest())
.And(x => GivenTheHouseReturns())
.And(x => WhenIBuild())
.When(x => WhenICallTheClient())
.Then(x => ThenTheFakeAreHandledInOrder(fakeOne, fakeTwo))
.And(x => ThenSomethingIsReturned())
.BDDfy();
}
private void GivenARequest()
{
_request = new Ocelot.Request.Request(null, false, null, false, false, "", false);
}
private void GivenTheHouseReturns()
{
_house
.Setup(x => x.Get(It.IsAny<Ocelot.Request.Request>()))
.Returns(new OkResponse<IDelegatingHandlerHandlerProvider>(_provider.Object));
}
private void ThenSomethingIsReturned()
{
_response.ShouldNotBeNull();
}
private void WhenICallTheClient()
{
_response = _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, "http://test.com")).GetAwaiter().GetResult();
}
private void ThenTheFakeAreHandledInOrder(FakeDelegatingHandler fakeOne, FakeDelegatingHandler fakeTwo)
{
fakeOne.TimeCalled.ShouldBeGreaterThan(fakeTwo.TimeCalled);
}
private void GivenTheProviderReturns()
{
_provider
.Setup(x => x.Get())
.Returns(new List<Func<DelegatingHandler>>(){ () => new FakeDelegatingHandler()});
}
private void GivenTheProviderReturns(List<Func<DelegatingHandler>> handlers)
{
_provider
.Setup(x => x.Get())
.Returns(handlers);
}
private void WhenIBuild()
{
_httpClient = _builder.Create(_request);
}
private void ThenTheHttpClientShouldNotBeNull()
{
_httpClient.ShouldNotBeNull();
}
}
}

View File

@ -0,0 +1,81 @@
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;
using Shouldly;
namespace Ocelot.UnitTests.Requester
{
public class HttpClientHttpRequesterTest
{
private readonly Mock<IHttpClientCache> _cacheHandlers;
private Mock<IDelegatingHandlerHandlerHouse> _house;
private Mock<IDelegatingHandlerHandlerProvider> _provider;
private Response<HttpResponseMessage> _response;
private readonly HttpClientHttpRequester _httpClientRequester;
private Ocelot.Request.Request _request;
private Mock<IOcelotLoggerFactory> _loggerFactory;
private Mock<IOcelotLogger> _logger;
public HttpClientHttpRequesterTest()
{
_provider = new Mock<IDelegatingHandlerHandlerProvider>();
_provider.Setup(x => x.Get()).Returns(new List<Func<DelegatingHandler>>());
_house = new Mock<IDelegatingHandlerHandlerHouse>();
_house.Setup(x => x.Get(It.IsAny<Ocelot.Request.Request>())).Returns(new OkResponse<IDelegatingHandlerHandlerProvider>(_provider.Object));
_logger = new Mock<IOcelotLogger>();
_loggerFactory = new Mock<IOcelotLoggerFactory>();
_loggerFactory
.Setup(x => x.CreateLogger<HttpClientHttpRequester>())
.Returns(_logger.Object);
_cacheHandlers = new Mock<IHttpClientCache>();
_httpClientRequester = new HttpClientHttpRequester(_loggerFactory.Object, _cacheHandlers.Object, _house.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_unable_to_complete_request()
{
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()
{
_response.IsError.ShouldBeFalse();
}
private void ThenTheResponseIsCalledError()
{
_response.IsError.ShouldBeTrue();
}
}
}

View File

@ -1,4 +1,4 @@
namespace Ocelot.UnitTests.Requester namespace Ocelot.UnitTests.Requester
{ {
using System.Net.Http; using System.Net.Http;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
@ -28,7 +28,7 @@
[Fact] [Fact]
public void should_call_scoped_data_repository_correctly() public void should_call_scoped_data_repository_correctly()
{ {
this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage(),true, new NoQoSProvider(), false, false))) 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.GivenTheRequesterReturns(new HttpResponseMessage()))
.And(x => x.GivenTheScopedRepoReturns()) .And(x => x.GivenTheScopedRepoReturns())
.When(x => x.WhenICallTheMiddleware()) .When(x => x.WhenICallTheMiddleware())

View File

@ -1,10 +1,8 @@
using Moq; using Moq;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Configuration.Builder; using Ocelot.Configuration.Builder;
using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Requester.QoS; using Ocelot.Requester.QoS;
using Ocelot.Responses; using Ocelot.Responses;
using Ocelot.UnitTests.LoadBalancer;
using Shouldly; using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;

View File

@ -121,7 +121,7 @@ namespace Ocelot.UnitTests.Responder
// If this test fails then it's because the number of error codes has changed. // If this test fails then it's because the number of error codes has changed.
// You should make the appropriate changes to the test cases here to ensure // You should make the appropriate changes to the test cases here to ensure
// they cover all the error codes, and then modify this assertion. // they cover all the error codes, and then modify this assertion.
Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(32, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?"); Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(33, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?");
} }
private void ShouldMapErrorToStatusCode(OcelotErrorCode errorCode, HttpStatusCode expectedHttpStatusCode) private void ShouldMapErrorToStatusCode(OcelotErrorCode errorCode, HttpStatusCode expectedHttpStatusCode)

View File

@ -1,10 +1,14 @@
namespace Ocelot.UnitTests.Responder namespace Ocelot.UnitTests.Responder
{ {
using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Moq; using Moq;
using Ocelot.DownstreamRouteFinder.Finder;
using Ocelot.Errors;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Requester;
using Ocelot.Responder; using Ocelot.Responder;
using Ocelot.Responder.Middleware; using Ocelot.Responder.Middleware;
using Ocelot.Responses; using Ocelot.Responses;
@ -35,6 +39,17 @@
.BDDfy(); .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) protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services)
{ {
services.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>(); services.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
@ -68,5 +83,14 @@
{ {
//todo a better assert? //todo a better assert?
} }
private void GivenThereArePipelineErrors(Error error)
{
ScopedRepository
.Setup(x => x.Get<bool>("OcelotMiddlewareError"))
.Returns(new OkResponse<bool>(true));
ScopedRepository.Setup(x => x.Get<List<Error>>("OcelotMiddlewareErrors"))
.Returns(new OkResponse<List<Error>>(new List<Error>() { error }));
}
} }
} }

View File

@ -0,0 +1,212 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Consul;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Moq;
using Ocelot.Logging;
using Ocelot.ServiceDiscovery;
using Ocelot.Values;
using Xunit;
using TestStack.BDDfy;
using Shouldly;
namespace Ocelot.UnitTests.ServiceDiscovery
{
public class ConsulServiceDiscoveryProviderTests : IDisposable
{
private IWebHost _fakeConsulBuilder;
private readonly List<ServiceEntry> _serviceEntries;
private readonly ConsulServiceDiscoveryProvider _provider;
private readonly string _serviceName;
private readonly int _port;
private readonly string _consulHost;
private readonly string _fakeConsulServiceDiscoveryUrl;
private List<Service> _services;
private Mock<IOcelotLoggerFactory> _factory;
private readonly Mock<IOcelotLogger> _logger;
public ConsulServiceDiscoveryProviderTests()
{
_serviceName = "test";
_port = 8500;
_consulHost = "localhost";
_fakeConsulServiceDiscoveryUrl = $"http://{_consulHost}:{_port}";
_serviceEntries = new List<ServiceEntry>();
_factory = new Mock<IOcelotLoggerFactory>();
_logger = new Mock<IOcelotLogger>();
_factory.Setup(x => x.CreateLogger<ConsulServiceDiscoveryProvider>()).Returns(_logger.Object);
var config = new ConsulRegistryConfiguration(_consulHost, _port, _serviceName);
_provider = new ConsulServiceDiscoveryProvider(config, _factory.Object);
}
[Fact]
public void should_return_service_from_consul()
{
var serviceEntryOne = new ServiceEntry()
{
Service = new AgentService()
{
Service = _serviceName,
Address = "localhost",
Port = 50881,
ID = Guid.NewGuid().ToString(),
Tags = new string[0]
},
};
this.Given(x =>GivenThereIsAFakeConsulServiceDiscoveryProvider(_fakeConsulServiceDiscoveryUrl, _serviceName))
.And(x => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne))
.When(x => WhenIGetTheServices())
.Then(x => ThenTheCountIs(1))
.BDDfy();
}
[Fact]
public void should_not_return_services_with_invalid_address()
{
var serviceEntryOne = new ServiceEntry()
{
Service = new AgentService()
{
Service = _serviceName,
Address = "http://localhost",
Port = 50881,
ID = Guid.NewGuid().ToString(),
Tags = new string[0]
},
};
var serviceEntryTwo = new ServiceEntry()
{
Service = new AgentService()
{
Service = _serviceName,
Address = "http://localhost",
Port = 50888,
ID = Guid.NewGuid().ToString(),
Tags = new string[0]
},
};
this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(_fakeConsulServiceDiscoveryUrl, _serviceName))
.And(x => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo))
.When(x => WhenIGetTheServices())
.Then(x => ThenTheCountIs(0))
.And(x => ThenTheLoggerHasBeenCalledCorrectlyForInvalidAddress())
.BDDfy();
}
[Fact]
public void should_not_return_services_with_invalid_port()
{
var serviceEntryOne = new ServiceEntry()
{
Service = new AgentService()
{
Service = _serviceName,
Address = "localhost",
Port = -1,
ID = Guid.NewGuid().ToString(),
Tags = new string[0]
},
};
var serviceEntryTwo = new ServiceEntry()
{
Service = new AgentService()
{
Service = _serviceName,
Address = "localhost",
Port = 0,
ID = Guid.NewGuid().ToString(),
Tags = new string[0]
},
};
this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(_fakeConsulServiceDiscoveryUrl, _serviceName))
.And(x => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo))
.When(x => WhenIGetTheServices())
.Then(x => ThenTheCountIs(0))
.And(x => ThenTheLoggerHasBeenCalledCorrectlyForInvalidPorts())
.BDDfy();
}
private void ThenTheLoggerHasBeenCalledCorrectlyForInvalidAddress()
{
_logger.Verify(
x => x.LogError(
"Unable to use service Address: http://localhost and Port: 50881 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"),
Times.Once);
_logger.Verify(
x => x.LogError(
"Unable to use service Address: http://localhost and Port: 50888 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"),
Times.Once);
}
private void ThenTheLoggerHasBeenCalledCorrectlyForInvalidPorts()
{
_logger.Verify(
x => x.LogError(
"Unable to use service Address: localhost and Port: -1 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"),
Times.Once);
_logger.Verify(
x => x.LogError(
"Unable to use service Address: localhost and Port: 0 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"),
Times.Once);
}
private void ThenTheCountIs(int count)
{
_services.Count.ShouldBe(count);
}
private void WhenIGetTheServices()
{
_services = _provider.Get().GetAwaiter().GetResult();
}
private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] serviceEntries)
{
foreach (var serviceEntry in serviceEntries)
{
_serviceEntries.Add(serviceEntry);
}
}
private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string serviceName)
{
_fakeConsulBuilder = new WebHostBuilder()
.UseUrls(url)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls(url)
.Configure(app =>
{
app.Run(async context =>
{
if (context.Request.Path.Value == $"/v1/health/service/{serviceName}")
{
await context.Response.WriteJsonAsync(_serviceEntries);
}
});
})
.Build();
_fakeConsulBuilder.Start();
}
public void Dispose()
{
_fakeConsulBuilder?.Dispose();
}
}
}

View File

@ -1,7 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Moq;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Configuration.Builder; using Ocelot.Configuration.Builder;
using Ocelot.Logging;
using Ocelot.ServiceDiscovery; using Ocelot.ServiceDiscovery;
using Shouldly; using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
@ -15,10 +17,12 @@ namespace Ocelot.UnitTests.ServiceDiscovery
private IServiceDiscoveryProvider _result; private IServiceDiscoveryProvider _result;
private readonly ServiceDiscoveryProviderFactory _factory; private readonly ServiceDiscoveryProviderFactory _factory;
private ReRoute _reRoute; private ReRoute _reRoute;
private Mock<IOcelotLoggerFactory> _loggerFactory;
public ServiceProviderFactoryTests() public ServiceProviderFactoryTests()
{ {
_factory = new ServiceDiscoveryProviderFactory(); _loggerFactory = new Mock<IOcelotLoggerFactory>();
_factory = new ServiceDiscoveryProviderFactory(_loggerFactory.Object);
} }
[Fact] [Fact]