Merge pull request #38 from TomPallister/develop

Merge Tracing
This commit is contained in:
geffzhang 2018-02-14 09:50:29 +08:00 committed by GitHub
commit 57d7b9dfde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
75 changed files with 2851 additions and 1195 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

@ -2,6 +2,9 @@
[![Build status](https://ci.appveyor.com/api/projects/status/r6sv51qx36sis1je?svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb) [![Build status](https://ci.appveyor.com/api/projects/status/r6sv51qx36sis1je?svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb)
[![Windows Build history](https://buildstats.info/appveyor/chart/TomPallister/ocelot-fcfpb?branch=develop&includeBuildsFromPullRequest=false)](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb/history?branch=develop)
[![Coverage Status](https://coveralls.io/repos/github/TomPallister/Ocelot/badge.svg?branch=develop)](https://coveralls.io/github/TomPallister/Ocelot?branch=develop) [![Coverage Status](https://coveralls.io/repos/github/TomPallister/Ocelot/badge.svg?branch=develop)](https://coveralls.io/github/TomPallister/Ocelot?branch=develop)
Ocelot is a .NET Api Gateway. This project is aimed at people using .NET running Ocelot is a .NET Api Gateway. This project is aimed at people using .NET running

View File

@ -421,6 +421,8 @@ private void PublishPackages(ConvertableDirectoryPath packagesDir, ConvertableFi
var codePackage = packagesDir + File(artifacts["nuget"]); var codePackage = packagesDir + File(artifacts["nuget"]);
Information("Pushing package " + codePackage); Information("Pushing package " + codePackage);
Information("Calling NuGetPush");
NuGetPush( NuGetPush(
codePackage, codePackage,

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)
@ -231,4 +232,4 @@ $cakeArguments += $ScriptArgs
# Start Cake # Start Cake
Write-Host "Running build script..." Write-Host "Running build script..."
&$CAKE_EXE $cakeArguments &$CAKE_EXE $cakeArguments
exit $LASTEXITCODE exit $LASTEXITCODE

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

@ -6,13 +6,16 @@ namespace Ocelot.Cache
public class CachedResponse public class CachedResponse
{ {
public CachedResponse( public CachedResponse(
HttpStatusCode statusCode = HttpStatusCode.OK, HttpStatusCode statusCode,
Dictionary<string, IEnumerable<string>> headers = null, Dictionary<string, IEnumerable<string>> headers,
string body = null string body,
Dictionary<string, IEnumerable<string>> contentHeaders
) )
{ {
StatusCode = statusCode; StatusCode = statusCode;
Headers = headers ?? new Dictionary<string, IEnumerable<string>>(); Headers = headers ?? new Dictionary<string, IEnumerable<string>>();
ContentHeaders = contentHeaders ?? new Dictionary<string, IEnumerable<string>>();
Body = body ?? ""; Body = body ?? "";
} }
@ -20,6 +23,8 @@ namespace Ocelot.Cache
public Dictionary<string, IEnumerable<string>> Headers { get; private set; } public Dictionary<string, IEnumerable<string>> Headers { get; private set; }
public Dictionary<string, IEnumerable<string>> ContentHeaders { get; private set; }
public string Body { get; private set; } public string Body { get; private set; }
} }
} }

View File

@ -82,13 +82,21 @@ namespace Ocelot.Cache.Middleware
} }
var response = new HttpResponseMessage(cached.StatusCode); var response = new HttpResponseMessage(cached.StatusCode);
foreach (var header in cached.Headers) foreach (var header in cached.Headers)
{ {
response.Headers.Add(header.Key, header.Value); response.Headers.Add(header.Key, header.Value);
} }
var content = new MemoryStream(Convert.FromBase64String(cached.Body)); var content = new MemoryStream(Convert.FromBase64String(cached.Body));
response.Content = new StreamContent(content); response.Content = new StreamContent(content);
foreach (var header in cached.ContentHeaders)
{
response.Content.Headers.Add(header.Key, header.Value);
}
return response; return response;
} }
@ -109,7 +117,10 @@ namespace Ocelot.Cache.Middleware
body = Convert.ToBase64String(content); body = Convert.ToBase64String(content);
} }
var cached = new CachedResponse(statusCode, headers, body); var contentHeaders = response?.Content?.Headers.ToDictionary(v => v.Key, v => v.Value);
var cached = new CachedResponse(statusCode, headers, body, contentHeaders);
return cached; return cached;
} }
} }

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,12 +1,16 @@
using CacheManager.Core; using Butterfly.Client.AspNetCore;
using System; using CacheManager.Core;
using System;
namespace Ocelot.DependencyInjection using System.Net.Http;
{
public interface IOcelotBuilder namespace Ocelot.DependencyInjection
{ {
IOcelotBuilder AddStoreOcelotConfigurationInConsul(); public interface IOcelotBuilder
IOcelotBuilder AddCacheManager(Action<ConfigurationBuilderCachePart> settings); {
IOcelotAdministrationBuilder AddAdministration(string path, string secret); IOcelotBuilder AddStoreOcelotConfigurationInConsul();
} IOcelotBuilder AddCacheManager(Action<ConfigurationBuilderCachePart> settings);
} IOcelotBuilder AddOpenTracing(Action<ButterflyOptions> settings);
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

@ -1,307 +1,295 @@
using CacheManager.Core; using CacheManager.Core;
using IdentityServer4.Models; using IdentityServer4.Models;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Ocelot.Authorisation; using Ocelot.Authorisation;
using Ocelot.Cache; using Ocelot.Cache;
using Ocelot.Claims; using Ocelot.Claims;
using Ocelot.Configuration.Authentication; using Ocelot.Configuration.Authentication;
using Ocelot.Configuration.Creator; using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.Configuration.Parser; using Ocelot.Configuration.Parser;
using Ocelot.Configuration.Provider; using Ocelot.Configuration.Provider;
using Ocelot.Configuration.Repository; using Ocelot.Configuration.Repository;
using Ocelot.Configuration.Setter; using Ocelot.Configuration.Setter;
using Ocelot.Configuration.Validator; using Ocelot.Configuration.Validator;
using Ocelot.DownstreamRouteFinder.Finder; using Ocelot.DownstreamRouteFinder.Finder;
using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.DownstreamUrlCreator; using Ocelot.DownstreamUrlCreator;
using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
using Ocelot.Headers; using Ocelot.Headers;
using Ocelot.Infrastructure.Claims.Parser; using Ocelot.Infrastructure.Claims.Parser;
using Ocelot.Infrastructure.RequestData; using Ocelot.Infrastructure.RequestData;
using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.QueryStrings; using Ocelot.QueryStrings;
using Ocelot.RateLimit; using Ocelot.RateLimit;
using Ocelot.Request.Builder; using Ocelot.Request.Builder;
using Ocelot.Request.Mapper; using Ocelot.Request.Mapper;
using Ocelot.Requester; using Ocelot.Requester;
using Ocelot.Requester.QoS; using Ocelot.Requester.QoS;
using Ocelot.Responder; using Ocelot.Responder;
using Ocelot.ServiceDiscovery; 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; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting; using Ocelot.Configuration;
using Ocelot.Configuration; using Ocelot.Configuration.Builder;
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 System.Net.Http;
using Ocelot.Raft; using Butterfly.Client.AspNetCore;
using Rafty.Concensus;
using Rafty.FiniteStateMachine; namespace Ocelot.DependencyInjection
using Rafty.Infrastructure; {
using Rafty.Log; public class OcelotBuilder : IOcelotBuilder
using Newtonsoft.Json; {
private readonly IServiceCollection _services;
namespace Ocelot.DependencyInjection private readonly IConfiguration _configurationRoot;
{ private IDelegatingHandlerHandlerProvider _provider;
public class OcelotBuilder : IOcelotBuilder
{ public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot)
private IServiceCollection _services; {
private IConfiguration _configurationRoot; _configurationRoot = configurationRoot;
_services = services;
public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot)
{ //add default cache settings...
_configurationRoot = configurationRoot; Action<ConfigurationBuilderCachePart> defaultCachingSettings = x =>
_services = services; {
x.WithDictionaryHandle();
//add default cache settings... };
Action<ConfigurationBuilderCachePart> defaultCachingSettings = x =>
{ AddCacheManager(defaultCachingSettings);
x.WithDictionaryHandle();
}; //add ocelot services...
_services.Configure<FileConfiguration>(configurationRoot);
AddCacheManager(defaultCachingSettings); _services.TryAddSingleton<IHttpResponseHeaderReplacer, HttpResponseHeaderReplacer>();
_services.TryAddSingleton<IHttpContextRequestHeaderReplacer, HttpContextRequestHeaderReplacer>();
//add ocelot services... _services.TryAddSingleton<IHeaderFindAndReplaceCreator, HeaderFindAndReplaceCreator>();
_services.Configure<FileConfiguration>(configurationRoot); _services.TryAddSingleton<IOcelotConfigurationCreator, FileOcelotConfigurationCreator>();
_services.TryAddSingleton<IHttpResponseHeaderReplacer, HttpResponseHeaderReplacer>(); _services.TryAddSingleton<IOcelotConfigurationRepository, InMemoryOcelotConfigurationRepository>();
_services.TryAddSingleton<IHttpContextRequestHeaderReplacer, HttpContextRequestHeaderReplacer>(); _services.TryAddSingleton<IConfigurationValidator, FileConfigurationFluentValidator>();
_services.TryAddSingleton<IHeaderFindAndReplaceCreator, HeaderFindAndReplaceCreator>(); _services.TryAddSingleton<IClaimsToThingCreator, ClaimsToThingCreator>();
_services.TryAddSingleton<IOcelotConfigurationCreator, FileOcelotConfigurationCreator>(); _services.TryAddSingleton<IAuthenticationOptionsCreator, AuthenticationOptionsCreator>();
_services.TryAddSingleton<IOcelotConfigurationRepository, InMemoryOcelotConfigurationRepository>(); _services.TryAddSingleton<IUpstreamTemplatePatternCreator, UpstreamTemplatePatternCreator>();
_services.TryAddSingleton<IConfigurationValidator, FileConfigurationFluentValidator>(); _services.TryAddSingleton<IRequestIdKeyCreator, RequestIdKeyCreator>();
_services.TryAddSingleton<IClaimsToThingCreator, ClaimsToThingCreator>(); _services.TryAddSingleton<IServiceProviderConfigurationCreator,ServiceProviderConfigurationCreator>();
_services.TryAddSingleton<IAuthenticationOptionsCreator, AuthenticationOptionsCreator>(); _services.TryAddSingleton<IQoSOptionsCreator, QoSOptionsCreator>();
_services.TryAddSingleton<IUpstreamTemplatePatternCreator, UpstreamTemplatePatternCreator>(); _services.TryAddSingleton<IReRouteOptionsCreator, ReRouteOptionsCreator>();
_services.TryAddSingleton<IRequestIdKeyCreator, RequestIdKeyCreator>(); _services.TryAddSingleton<IRateLimitOptionsCreator, RateLimitOptionsCreator>();
_services.TryAddSingleton<IServiceProviderConfigurationCreator,ServiceProviderConfigurationCreator>(); _services.TryAddSingleton<IBaseUrlFinder, BaseUrlFinder>();
_services.TryAddSingleton<IQoSOptionsCreator, QoSOptionsCreator>(); _services.TryAddSingleton<IRegionCreator, RegionCreator>();
_services.TryAddSingleton<IReRouteOptionsCreator, ReRouteOptionsCreator>(); _services.TryAddSingleton<IFileConfigurationRepository, FileConfigurationRepository>();
_services.TryAddSingleton<IRateLimitOptionsCreator, RateLimitOptionsCreator>(); _services.TryAddSingleton<IFileConfigurationSetter, FileConfigurationSetter>();
_services.TryAddSingleton<IBaseUrlFinder, BaseUrlFinder>(); _services.TryAddSingleton<IFileConfigurationProvider, FileConfigurationProvider>();
_services.TryAddSingleton<IRegionCreator, RegionCreator>(); _services.TryAddSingleton<IQosProviderHouse, QosProviderHouse>();
_services.TryAddSingleton<IFileConfigurationRepository, FileConfigurationRepository>(); _services.TryAddSingleton<IQoSProviderFactory, QoSProviderFactory>();
_services.TryAddSingleton<IFileConfigurationSetter, FileConfigurationSetter>(); _services.TryAddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>();
_services.TryAddSingleton<IFileConfigurationProvider, FileConfigurationProvider>(); _services.TryAddSingleton<ILoadBalancerFactory, LoadBalancerFactory>();
_services.TryAddSingleton<IQosProviderHouse, QosProviderHouse>(); _services.TryAddSingleton<ILoadBalancerHouse, LoadBalancerHouse>();
_services.TryAddSingleton<IQoSProviderFactory, QoSProviderFactory>(); _services.TryAddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
_services.TryAddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>(); _services.TryAddSingleton<IUrlBuilder, UrlBuilder>();
_services.TryAddSingleton<ILoadBalancerFactory, LoadBalancerFactory>(); _services.TryAddSingleton<IRemoveOutputHeaders, RemoveOutputHeaders>();
_services.TryAddSingleton<ILoadBalancerHouse, LoadBalancerHouse>(); _services.TryAddSingleton<IOcelotConfigurationProvider, OcelotConfigurationProvider>();
_services.TryAddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>(); _services.TryAddSingleton<IClaimToThingConfigurationParser, ClaimToThingConfigurationParser>();
_services.TryAddSingleton<IUrlBuilder, UrlBuilder>(); _services.TryAddSingleton<IClaimsAuthoriser, ClaimsAuthoriser>();
_services.TryAddSingleton<IRemoveOutputHeaders, RemoveOutputHeaders>(); _services.TryAddSingleton<IScopesAuthoriser, ScopesAuthoriser>();
_services.TryAddSingleton<IOcelotConfigurationProvider, OcelotConfigurationProvider>(); _services.TryAddSingleton<IAddClaimsToRequest, AddClaimsToRequest>();
_services.TryAddSingleton<IClaimToThingConfigurationParser, ClaimToThingConfigurationParser>(); _services.TryAddSingleton<IAddHeadersToRequest, AddHeadersToRequest>();
_services.TryAddSingleton<IClaimsAuthoriser, ClaimsAuthoriser>(); _services.TryAddSingleton<IAddQueriesToRequest, AddQueriesToRequest>();
_services.TryAddSingleton<IScopesAuthoriser, ScopesAuthoriser>(); _services.TryAddSingleton<IClaimsParser, ClaimsParser>();
_services.TryAddSingleton<IAddClaimsToRequest, AddClaimsToRequest>(); _services.TryAddSingleton<IUrlPathToUrlTemplateMatcher, RegExUrlMatcher>();
_services.TryAddSingleton<IAddHeadersToRequest, AddHeadersToRequest>(); _services.TryAddSingleton<IPlaceholderNameAndValueFinder, UrlPathPlaceholderNameAndValueFinder>();
_services.TryAddSingleton<IAddQueriesToRequest, AddQueriesToRequest>(); _services.TryAddSingleton<IDownstreamPathPlaceholderReplacer, DownstreamTemplatePathPlaceholderReplacer>();
_services.TryAddSingleton<IClaimsParser, ClaimsParser>(); _services.TryAddSingleton<IDownstreamRouteFinder, DownstreamRouteFinder.Finder.DownstreamRouteFinder>();
_services.TryAddSingleton<IUrlPathToUrlTemplateMatcher, RegExUrlMatcher>(); _services.TryAddSingleton<IHttpRequester, HttpClientHttpRequester>();
_services.TryAddSingleton<IPlaceholderNameAndValueFinder, UrlPathPlaceholderNameAndValueFinder>(); _services.TryAddSingleton<IHttpResponder, HttpContextResponder>();
_services.TryAddSingleton<IDownstreamPathPlaceholderReplacer, DownstreamTemplatePathPlaceholderReplacer>(); _services.TryAddSingleton<IRequestCreator, HttpRequestCreator>();
_services.TryAddSingleton<IDownstreamRouteFinder, DownstreamRouteFinder.Finder.DownstreamRouteFinder>(); _services.TryAddSingleton<IErrorsToHttpStatusCodeMapper, ErrorsToHttpStatusCodeMapper>();
_services.TryAddSingleton<IHttpRequester, HttpClientHttpRequester>(); _services.TryAddSingleton<IRateLimitCounterHandler, MemoryCacheRateLimitCounterHandler>();
_services.TryAddSingleton<IHttpResponder, HttpContextResponder>(); _services.TryAddSingleton<IHttpClientCache, MemoryHttpClientCache>();
_services.TryAddSingleton<IRequestCreator, HttpRequestCreator>(); _services.TryAddSingleton<IRequestMapper, RequestMapper>();
_services.TryAddSingleton<IErrorsToHttpStatusCodeMapper, ErrorsToHttpStatusCodeMapper>(); _services.TryAddSingleton<IHttpHandlerOptionsCreator, HttpHandlerOptionsCreator>();
_services.TryAddSingleton<IRateLimitCounterHandler, MemoryCacheRateLimitCounterHandler>(); _services.TryAddSingleton<IDownstreamAddressesCreator, DownstreamAddressesCreator>();
_services.TryAddSingleton<IHttpClientCache, MemoryHttpClientCache>(); _services.TryAddSingleton<IDelegatingHandlerHandlerProviderFactory, DelegatingHandlerHandlerProviderFactory>();
_services.TryAddSingleton<IRequestMapper, RequestMapper>(); _services.TryAddSingleton<IDelegatingHandlerHandlerHouse, DelegatingHandlerHandlerHouse>();
_services.TryAddSingleton<IHttpHandlerOptionsCreator, HttpHandlerOptionsCreator>();
_services.TryAddSingleton<IDownstreamAddressesCreator, DownstreamAddressesCreator>(); // 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>(); _services.TryAddSingleton<IRequestScopedDataRepository, HttpDataRepository>();
_services.TryAddSingleton<IRequestScopedDataRepository, HttpDataRepository>(); _services.AddMemoryCache();
_services.AddMemoryCache(); _services.TryAddSingleton<OcelotDiagnosticListener>();
_services.TryAddSingleton<OcelotDiagnosticListener>();
//add asp.net services..
//add asp.net services.. var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly;
var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly;
_services.AddMvcCore()
_services.AddMvcCore() .AddApplicationPart(assembly)
.AddApplicationPart(assembly) .AddControllersAsServices()
.AddControllersAsServices() .AddAuthorization()
.AddAuthorization() .AddJsonFormatters();
.AddJsonFormatters();
_services.AddLogging();
_services.AddLogging(); _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();
public IOcelotAdministrationBuilder AddAdministration(string path, string secret) _services.TryAddSingleton<IDelegatingHandlerHandlerProvider>(_provider);
{ _services.AddTransient<ITracingHandler, NoTracingHandler>();
var administrationPath = new AdministrationPath(path); }
//add identity server for admin area public IOcelotAdministrationBuilder AddAdministration(string path, string secret)
var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(secret); {
var administrationPath = new AdministrationPath(path);
if (identityServerConfiguration != null)
{ //add identity server for admin area
AddIdentityServer(identityServerConfiguration, administrationPath); var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(secret);
}
if (identityServerConfiguration != null)
var descriptor = new ServiceDescriptor(typeof(IAdministrationPath), administrationPath); {
_services.Replace(descriptor); AddIdentityServer(identityServerConfiguration, administrationPath);
return new OcelotAdministrationBuilder(_services, _configurationRoot); }
}
var descriptor = new ServiceDescriptor(typeof(IAdministrationPath), administrationPath);
public IOcelotBuilder AddStoreOcelotConfigurationInConsul() _services.Replace(descriptor);
{ return new OcelotAdministrationBuilder(_services, _configurationRoot);
var serviceDiscoveryPort = _configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Port", 0); }
var serviceDiscoveryHost = _configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Host", string.Empty);
public IOcelotBuilder AddDelegatingHandler(Func<DelegatingHandler> delegatingHandler)
var config = new ServiceProviderConfigurationBuilder() {
.WithServiceDiscoveryProviderPort(serviceDiscoveryPort) _provider.Add(delegatingHandler);
.WithServiceDiscoveryProviderHost(serviceDiscoveryHost) return this;
.Build(); }
_services.AddSingleton<ServiceProviderConfiguration>(config); public IOcelotBuilder AddOpenTracing(Action<ButterflyOptions> settings)
_services.AddSingleton<ConsulFileConfigurationPoller>(); {
_services.AddSingleton<IFileConfigurationRepository, ConsulFileConfigurationRepository>(); _services.AddTransient<ITracingHandler, OcelotHttpTracingHandler>();
return this; _services.AddButterfly(settings);
} return this;
}
public IOcelotBuilder AddCacheManager(Action<ConfigurationBuilderCachePart> settings)
{ public IOcelotBuilder AddStoreOcelotConfigurationInConsul()
var cacheManagerOutputCache = CacheFactory.Build<CachedResponse>("OcelotOutputCache", settings); {
var ocelotOutputCacheManager = new OcelotCacheManagerCache<CachedResponse>(cacheManagerOutputCache); var serviceDiscoveryPort = _configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Port", 0);
var serviceDiscoveryHost = _configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Host", string.Empty);
_services.RemoveAll(typeof(ICacheManager<CachedResponse>));
_services.RemoveAll(typeof(IOcelotCache<CachedResponse>)); var config = new ServiceProviderConfigurationBuilder()
_services.AddSingleton<ICacheManager<CachedResponse>>(cacheManagerOutputCache); .WithServiceDiscoveryProviderPort(serviceDiscoveryPort)
_services.AddSingleton<IOcelotCache<CachedResponse>>(ocelotOutputCacheManager); .WithServiceDiscoveryProviderHost(serviceDiscoveryHost)
.Build();
var ocelotConfigCacheManagerOutputCache = CacheFactory.Build<IOcelotConfiguration>("OcelotConfigurationCache", settings);
var ocelotConfigCacheManager = new OcelotCacheManagerCache<IOcelotConfiguration>(ocelotConfigCacheManagerOutputCache); _services.AddSingleton<ServiceProviderConfiguration>(config);
_services.RemoveAll(typeof(ICacheManager<IOcelotConfiguration>)); _services.AddSingleton<ConsulFileConfigurationPoller>();
_services.RemoveAll(typeof(IOcelotCache<IOcelotConfiguration>)); _services.AddSingleton<IFileConfigurationRepository, ConsulFileConfigurationRepository>();
_services.AddSingleton<ICacheManager<IOcelotConfiguration>>(ocelotConfigCacheManagerOutputCache); return this;
_services.AddSingleton<IOcelotCache<IOcelotConfiguration>>(ocelotConfigCacheManager); }
var fileConfigCacheManagerOutputCache = CacheFactory.Build<FileConfiguration>("FileConfigurationCache", settings); public IOcelotBuilder AddCacheManager(Action<ConfigurationBuilderCachePart> settings)
var fileConfigCacheManager = new OcelotCacheManagerCache<FileConfiguration>(fileConfigCacheManagerOutputCache); {
_services.RemoveAll(typeof(ICacheManager<FileConfiguration>)); var cacheManagerOutputCache = CacheFactory.Build<CachedResponse>("OcelotOutputCache", settings);
_services.RemoveAll(typeof(IOcelotCache<FileConfiguration>)); var ocelotOutputCacheManager = new OcelotCacheManagerCache<CachedResponse>(cacheManagerOutputCache);
_services.AddSingleton<ICacheManager<FileConfiguration>>(fileConfigCacheManagerOutputCache);
_services.AddSingleton<IOcelotCache<FileConfiguration>>(fileConfigCacheManager); _services.RemoveAll(typeof(ICacheManager<CachedResponse>));
return this; _services.RemoveAll(typeof(IOcelotCache<CachedResponse>));
} _services.AddSingleton<ICacheManager<CachedResponse>>(cacheManagerOutputCache);
_services.AddSingleton<IOcelotCache<CachedResponse>>(ocelotOutputCacheManager);
private void AddIdentityServer(IIdentityServerConfiguration identityServerConfiguration, IAdministrationPath adminPath)
{ var ocelotConfigCacheManagerOutputCache = CacheFactory.Build<IOcelotConfiguration>("OcelotConfigurationCache", settings);
_services.TryAddSingleton<IIdentityServerConfiguration>(identityServerConfiguration); var ocelotConfigCacheManager = new OcelotCacheManagerCache<IOcelotConfiguration>(ocelotConfigCacheManagerOutputCache);
_services.TryAddSingleton<IHashMatcher, HashMatcher>(); _services.RemoveAll(typeof(ICacheManager<IOcelotConfiguration>));
var identityServerBuilder = _services _services.RemoveAll(typeof(IOcelotCache<IOcelotConfiguration>));
.AddIdentityServer(o => { _services.AddSingleton<ICacheManager<IOcelotConfiguration>>(ocelotConfigCacheManagerOutputCache);
o.IssuerUri = "Ocelot"; _services.AddSingleton<IOcelotCache<IOcelotConfiguration>>(ocelotConfigCacheManager);
})
.AddInMemoryApiResources(Resources(identityServerConfiguration)) var fileConfigCacheManagerOutputCache = CacheFactory.Build<FileConfiguration>("FileConfigurationCache", settings);
.AddInMemoryClients(Client(identityServerConfiguration)); var fileConfigCacheManager = new OcelotCacheManagerCache<FileConfiguration>(fileConfigCacheManagerOutputCache);
_services.RemoveAll(typeof(ICacheManager<FileConfiguration>));
//todo - refactor a method so we know why this is happening _services.RemoveAll(typeof(IOcelotCache<FileConfiguration>));
var whb = _services.First(x => x.ServiceType == typeof(IWebHostBuilder)); _services.AddSingleton<ICacheManager<FileConfiguration>>(fileConfigCacheManagerOutputCache);
var urlFinder = new BaseUrlFinder((IWebHostBuilder)whb.ImplementationInstance); _services.AddSingleton<IOcelotCache<FileConfiguration>>(fileConfigCacheManager);
var baseSchemeUrlAndPort = urlFinder.Find(); return this;
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); }
_services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) private void AddIdentityServer(IIdentityServerConfiguration identityServerConfiguration, IAdministrationPath adminPath)
.AddIdentityServerAuthentication(o => {
{ _services.TryAddSingleton<IIdentityServerConfiguration>(identityServerConfiguration);
o.Authority = baseSchemeUrlAndPort + adminPath.Path; _services.TryAddSingleton<IHashMatcher, HashMatcher>();
o.ApiName = identityServerConfiguration.ApiName; var identityServerBuilder = _services
o.RequireHttpsMetadata = identityServerConfiguration.RequireHttps; .AddIdentityServer(o => {
o.SupportedTokens = SupportedTokens.Both; o.IssuerUri = "Ocelot";
o.ApiSecret = identityServerConfiguration.ApiSecret; })
}); .AddInMemoryApiResources(Resources(identityServerConfiguration))
.AddInMemoryClients(Client(identityServerConfiguration));
//todo - refactor naming..
if (string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificateLocation) || string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificatePassword)) //todo - refactor a method so we know why this is happening
{ var whb = _services.First(x => x.ServiceType == typeof(IWebHostBuilder));
identityServerBuilder.AddDeveloperSigningCredential(); var urlFinder = new BaseUrlFinder((IWebHostBuilder)whb.ImplementationInstance);
} var baseSchemeUrlAndPort = urlFinder.Find();
else JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
{
//todo - refactor so calls method? _services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
var cert = new X509Certificate2(identityServerConfiguration.CredentialsSigningCertificateLocation, identityServerConfiguration.CredentialsSigningCertificatePassword); .AddIdentityServerAuthentication(o =>
identityServerBuilder.AddSigningCredential(cert); {
} o.Authority = baseSchemeUrlAndPort + adminPath.Path;
} o.ApiName = identityServerConfiguration.ApiName;
o.RequireHttpsMetadata = identityServerConfiguration.RequireHttps;
private List<ApiResource> Resources(IIdentityServerConfiguration identityServerConfiguration) o.SupportedTokens = SupportedTokens.Both;
{ o.ApiSecret = identityServerConfiguration.ApiSecret;
return new List<ApiResource> });
{
new ApiResource(identityServerConfiguration.ApiName, identityServerConfiguration.ApiName) //todo - refactor naming..
{ if (string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificateLocation) || string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificatePassword))
ApiSecrets = new List<Secret> {
{ identityServerBuilder.AddDeveloperSigningCredential();
new Secret }
{ else
Value = identityServerConfiguration.ApiSecret.Sha256() {
} //todo - refactor so calls method?
} var cert = new X509Certificate2(identityServerConfiguration.CredentialsSigningCertificateLocation, identityServerConfiguration.CredentialsSigningCertificatePassword);
}, identityServerBuilder.AddSigningCredential(cert);
}; }
} }
private List<Client> Client(IIdentityServerConfiguration identityServerConfiguration) private List<ApiResource> Resources(IIdentityServerConfiguration identityServerConfiguration)
{ {
return new List<Client> return new List<ApiResource>
{ {
new Client new ApiResource(identityServerConfiguration.ApiName, identityServerConfiguration.ApiName)
{ {
ClientId = identityServerConfiguration.ApiName, ApiSecrets = new List<Secret>
AllowedGrantTypes = GrantTypes.ClientCredentials, {
ClientSecrets = new List<Secret> {new Secret(identityServerConfiguration.ApiSecret.Sha256())}, new Secret
AllowedScopes = { identityServerConfiguration.ApiName } {
} Value = identityServerConfiguration.ApiSecret.Sha256()
}; }
} }
} },
};
public interface IOcelotAdministrationBuilder }
{
IOcelotAdministrationBuilder AddRafty(); private List<Client> Client(IIdentityServerConfiguration identityServerConfiguration)
} {
return new List<Client>
public class OcelotAdministrationBuilder : IOcelotAdministrationBuilder {
{ new Client
private IServiceCollection _services; {
private IConfiguration _configurationRoot; ClientId = identityServerConfiguration.ApiName,
AllowedGrantTypes = GrantTypes.ClientCredentials,
public OcelotAdministrationBuilder(IServiceCollection services, IConfiguration configurationRoot) ClientSecrets = new List<Secret> {new Secret(identityServerConfiguration.ApiSecret.Sha256())},
{ AllowedScopes = { identityServerConfiguration.ApiName }
_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

@ -9,4 +9,4 @@ namespace Ocelot.LoadBalancer.LoadBalancers
{ {
} }
} }
} }

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

@ -1,46 +1,47 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netcoreapp2.0</TargetFramework>
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion> <RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
<NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion> <NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
<Description>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.</Description> <Description>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.</Description>
<AssemblyTitle>Ocelot</AssemblyTitle> <AssemblyTitle>Ocelot</AssemblyTitle>
<VersionPrefix>0.0.0-dev</VersionPrefix> <VersionPrefix>0.0.0-dev</VersionPrefix>
<AssemblyName>Ocelot</AssemblyName> <AssemblyName>Ocelot</AssemblyName>
<PackageId>Ocelot</PackageId> <PackageId>Ocelot</PackageId>
<PackageTags>API Gateway;.NET core</PackageTags> <PackageTags>API Gateway;.NET core</PackageTags>
<PackageProjectUrl>https://github.com/TomPallister/Ocelot</PackageProjectUrl> <PackageProjectUrl>https://github.com/TomPallister/Ocelot</PackageProjectUrl>
<PackageProjectUrl>https://github.com/TomPallister/Ocelot</PackageProjectUrl> <PackageProjectUrl>https://github.com/TomPallister/Ocelot</PackageProjectUrl>
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers> <RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<Authors>Tom Pallister</Authors> <Authors>Tom Pallister</Authors>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>full</DebugType> <DebugType>full</DebugType>
<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" />
</ItemGroup> <PackageReference Include="Rafty" Version="0.4.2" />
</ItemGroup>
</Project> </Project>

View File

@ -1,20 +1,22 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Responses; using Ocelot.Responses;
using Ocelot.Requester.QoS; using Ocelot.Requester.QoS;
using System.Net.Http; using System.Net.Http;
namespace Ocelot.Request.Builder namespace Ocelot.Request.Builder
{ {
public sealed class HttpRequestCreator : IRequestCreator public sealed class HttpRequestCreator : IRequestCreator
{ {
public async Task<Response<Request>> Build( public async Task<Response<Request>> Build(
HttpRequestMessage httpRequestMessage, HttpRequestMessage httpRequestMessage,
bool isQos, bool isQos,
IQoSProvider qosProvider, IQoSProvider qosProvider,
bool useCookieContainer, bool useCookieContainer,
bool allowAutoRedirect) bool allowAutoRedirect,
{ string reRouteKey,
return new OkResponse<Request>(new Request(httpRequestMessage, isQos, qosProvider, allowAutoRedirect, useCookieContainer)); bool isTracing)
} {
} return new OkResponse<Request>(new Request(httpRequestMessage, isQos, qosProvider, allowAutoRedirect, useCookieContainer, reRouteKey, isTracing));
} }
}
}

View File

@ -1,18 +1,20 @@
namespace Ocelot.Request.Builder namespace Ocelot.Request.Builder
{ {
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Requester.QoS; using Ocelot.Requester.QoS;
using Ocelot.Responses; using Ocelot.Responses;
public interface IRequestCreator public interface IRequestCreator
{ {
Task<Response<Request>> Build( Task<Response<Request>> Build(
HttpRequestMessage httpRequestMessage, HttpRequestMessage httpRequestMessage,
bool isQos, bool isQos,
IQoSProvider qosProvider, IQoSProvider qosProvider,
bool useCookieContainer, bool useCookieContainer,
bool allowAutoRedirect); bool allowAutoRedirect,
} string reRouteKe,
} bool isTracing);
}
}

View File

@ -1,66 +1,67 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Infrastructure.RequestData; using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Request.Builder; using Ocelot.Request.Builder;
using Ocelot.Requester.QoS; using Ocelot.Requester.QoS;
namespace Ocelot.Request.Middleware namespace Ocelot.Request.Middleware
{ {
public class HttpRequestBuilderMiddleware : OcelotMiddleware public class HttpRequestBuilderMiddleware : OcelotMiddleware
{ {
private readonly RequestDelegate _next; private readonly RequestDelegate _next;
private readonly IRequestCreator _requestCreator; private readonly IRequestCreator _requestCreator;
private readonly IOcelotLogger _logger; private readonly IOcelotLogger _logger;
private readonly IQosProviderHouse _qosProviderHouse; private readonly IQosProviderHouse _qosProviderHouse;
public HttpRequestBuilderMiddleware(RequestDelegate next, public HttpRequestBuilderMiddleware(RequestDelegate next,
IOcelotLoggerFactory loggerFactory, IOcelotLoggerFactory loggerFactory,
IRequestScopedDataRepository requestScopedDataRepository, IRequestScopedDataRepository requestScopedDataRepository,
IRequestCreator requestCreator, IRequestCreator requestCreator,
IQosProviderHouse qosProviderHouse) IQosProviderHouse qosProviderHouse)
:base(requestScopedDataRepository) :base(requestScopedDataRepository)
{ {
_next = next; _next = next;
_requestCreator = requestCreator; _requestCreator = requestCreator;
_qosProviderHouse = qosProviderHouse; _qosProviderHouse = qosProviderHouse;
_logger = loggerFactory.CreateLogger<HttpRequestBuilderMiddleware>(); _logger = loggerFactory.CreateLogger<HttpRequestBuilderMiddleware>();
} }
public async Task Invoke(HttpContext context) public async Task Invoke(HttpContext context)
{ {
var qosProvider = _qosProviderHouse.Get(DownstreamRoute.ReRoute); var qosProvider = _qosProviderHouse.Get(DownstreamRoute.ReRoute);
if (qosProvider.IsError) if (qosProvider.IsError)
{ {
_logger.LogDebug("IQosProviderHouse returned an error, setting pipeline error"); _logger.LogDebug("IQosProviderHouse returned an error, setting pipeline error");
SetPipelineError(qosProvider.Errors); SetPipelineError(qosProvider.Errors);
return; return;
} }
var buildResult = await _requestCreator.Build( var buildResult = await _requestCreator.Build(
DownstreamRequest, DownstreamRequest,
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,
if (buildResult.IsError) DownstreamRoute.ReRoute.HttpHandlerOptions.UseTracing);
{
_logger.LogDebug("IRequestCreator returned an error, setting pipeline error"); if (buildResult.IsError)
{
SetPipelineError(buildResult.Errors); _logger.LogDebug("IRequestCreator returned an error, setting pipeline error");
SetPipelineError(buildResult.Errors);
return; return;
} }
_logger.LogDebug("setting upstream request");
_logger.LogDebug("setting upstream request");
SetUpstreamRequestForThisRequest(buildResult.Data);
SetUpstreamRequestForThisRequest(buildResult.Data);
await _next.Invoke(context);
} await _next.Invoke(context);
} }
} }
}

View File

@ -1,28 +1,35 @@
using System.Net.Http; using System.Net.Http;
using Ocelot.Requester.QoS; using Ocelot.Requester.QoS;
namespace Ocelot.Request namespace Ocelot.Request
{ {
public class Request public class Request
{ {
public Request( public Request(
HttpRequestMessage httpRequestMessage, HttpRequestMessage httpRequestMessage,
bool isQos, bool isQos,
IQoSProvider qosProvider, IQoSProvider qosProvider,
bool allowAutoRedirect, bool allowAutoRedirect,
bool useCookieContainer) bool useCookieContainer,
{ string reRouteKey,
HttpRequestMessage = httpRequestMessage; bool isTracing
IsQos = isQos; )
QosProvider = qosProvider; {
AllowAutoRedirect = allowAutoRedirect; HttpRequestMessage = httpRequestMessage;
UseCookieContainer = useCookieContainer; IsQos = isQos;
} QosProvider = qosProvider;
AllowAutoRedirect = allowAutoRedirect;
public HttpRequestMessage HttpRequestMessage { get; private set; } UseCookieContainer = useCookieContainer;
public bool IsQos { get; private set; } ReRouteKey = reRouteKey;
public IQoSProvider QosProvider { get; private set; } IsTracing = isTracing;
public bool AllowAutoRedirect { get; private set; } }
public bool UseCookieContainer { get; private set; }
} 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; }
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,66 +1,42 @@
using System; using System.Linq;
using System.Collections.Generic; using System.Net.Http;
using System.Linq;
using System.Net; namespace Ocelot.Requester
using System.Net.Http; {
using System.Threading.Tasks; public class HttpClientBuilder : IHttpClientBuilder
using Ocelot.Logging; {
using Ocelot.Requester.QoS; private readonly IDelegatingHandlerHandlerHouse _house;
namespace Ocelot.Requester public HttpClientBuilder(IDelegatingHandlerHandlerHouse house)
{ {
internal class HttpClientBuilder : IHttpClientBuilder _house = house;
{ }
private readonly Dictionary<int, Func<DelegatingHandler>> _handlers = new Dictionary<int, Func<DelegatingHandler>>();
public IHttpClient Create(Request.Request request)
public IHttpClientBuilder WithQos(IQoSProvider qosProvider, IOcelotLogger logger) {
{ var httpclientHandler = new HttpClientHandler { AllowAutoRedirect = request.AllowAutoRedirect, UseCookies = request.UseCookieContainer};
_handlers.Add(5000, () => new PollyCircuitBreakingDelegatingHandler(qosProvider, logger));
var client = new HttpClient(CreateHttpMessageHandler(httpclientHandler, request));
return this;
} return new HttpClientWrapper(client);
}
public IHttpClient Create(bool useCookies, bool allowAutoRedirect)
{ private HttpMessageHandler CreateHttpMessageHandler(HttpMessageHandler httpMessageHandler, Request.Request request)
var httpclientHandler = new HttpClientHandler { AllowAutoRedirect = allowAutoRedirect, UseCookies = useCookies}; {
var provider = _house.Get(request);
var client = new HttpClient(CreateHttpMessageHandler(httpclientHandler));
//todo handle error
return new HttpClientWrapper(client); provider.Data.Get()
} .Select(handler => handler)
.Reverse()
private HttpMessageHandler CreateHttpMessageHandler(HttpMessageHandler httpMessageHandler) .ToList()
{ .ForEach(handler =>
_handlers {
.OrderByDescending(handler => handler.Key) var delegatingHandler = handler();
.Select(handler => handler.Value) delegatingHandler.InnerHandler = httpMessageHandler;
.Reverse() httpMessageHandler = delegatingHandler;
.ToList() });
.ForEach(handler => return httpMessageHandler;
{ }
var delegatingHandler = handler(); }
delegatingHandler.InnerHandler = httpMessageHandler; }
httpMessageHandler = delegatingHandler;
});
return httpMessageHandler;
}
}
/// <summary>
/// This class was made to make unit testing easier when HttpClient is used.
/// </summary>
internal class HttpClientWrapper : IHttpClient
{
public HttpClient Client { get; }
public HttpClientWrapper(HttpClient client)
{
Client = client;
}
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
{
return Client.SendAsync(request);
}
}
}

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,28 +57,28 @@ 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}";
} }
return baseUrl; return baseUrl;
} }
} }
} }

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> /// <summary>
/// Sets a PollyCircuitBreakingDelegatingHandler .
/// </summary>
IHttpClientBuilder WithQos(IQoSProvider qosProvider, IOcelotLogger logger);
/// <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;
@ -25,7 +25,7 @@ namespace Ocelot.Requester.Middleware
} }
public async Task Invoke(HttpContext context) public async Task Invoke(HttpContext context)
{ {
var response = await _requester.GetResponse(Request); var response = await _requester.GetResponse(Request);
if (response.IsError) if (response.IsError)
@ -41,4 +41,4 @@ namespace Ocelot.Requester.Middleware
SetHttpResponseMessageThisRequest(response.Data); SetHttpResponseMessageThisRequest(response.Data);
} }
} }
} }

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

@ -55,7 +55,7 @@ namespace Ocelot.Responder
using (Stream stream = new MemoryStream(content)) using (Stream stream = new MemoryStream(content))
{ {
if (response.StatusCode != HttpStatusCode.NotModified) if (response.StatusCode != HttpStatusCode.NotModified && context.Response.ContentLength != 0)
{ {
await stream.CopyToAsync(context.Response.Body); await stream.CopyToAsync(context.Response.Body);
} }
@ -79,4 +79,4 @@ namespace Ocelot.Responder
} }
} }
} }
} }

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;
@ -33,14 +34,13 @@ namespace Ocelot.Responder.Middleware
} }
public async Task Invoke(HttpContext context) public async Task Invoke(HttpContext context)
{ {
await _next.Invoke(context); await _next.Invoke(context);
if (PipelineError) if (PipelineError)
{ {
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
@ -50,10 +50,10 @@ namespace Ocelot.Responder.Middleware
} }
} }
private void SetErrorResponse(HttpContext context, List<Error> errors) private void SetErrorResponse(HttpContext context, List<Error> errors)
{ {
var statusCode = _codeMapper.Map(errors); var statusCode = _codeMapper.Map(errors);
_responder.SetErrorResponseOnContext(context, statusCode); _responder.SetErrorResponseOnContext(context, statusCode);
} }
} }
} }

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

@ -61,6 +61,7 @@ namespace Ocelot.AcceptanceTests
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.And(x => _steps.ThenTheContentLengthIs(16))
.BDDfy(); .BDDfy();
} }

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>
@ -425,5 +457,10 @@ namespace Ocelot.AcceptanceTests
{ {
_response.Headers.GetValues(RequestIdKey).First().ShouldBe(expected); _response.Headers.GetValues(RequestIdKey).First().ShouldBe(expected);
} }
public void ThenTheContentLengthIs(int expected)
{
_response.Content.Headers.ContentLength.ShouldBe(expected);
}
} }
} }

View File

@ -1,52 +1,53 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using Ocelot.DependencyInjection; using Ocelot.DependencyInjection;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Raft; using Ocelot.Raft;
using Rafty.Concensus; using Rafty.Concensus;
using Rafty.FiniteStateMachine; using Rafty.FiniteStateMachine;
using Rafty.Infrastructure; using Rafty.Infrastructure;
using Rafty.Log; using Rafty.Log;
using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder;
namespace Ocelot.IntegrationTests namespace Ocelot.IntegrationTests
{ {
public class RaftStartup public class RaftStartup
{ {
public RaftStartup(IHostingEnvironment env) public RaftStartup(IHostingEnvironment env)
{ {
var builder = new ConfigurationBuilder() var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath) .SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddJsonFile("peers.json", optional: true, reloadOnChange: true) .AddJsonFile("peers.json", optional: true, reloadOnChange: true)
.AddJsonFile("configuration.json") .AddJsonFile("configuration.json")
.AddEnvironmentVariables(); .AddEnvironmentVariables();
Configuration = builder.Build(); Configuration = builder.Build();
} }
public IConfiguration Configuration { get; } public IConfiguration Configuration { get; }
public virtual void ConfigureServices(IServiceCollection services) public virtual void ConfigureServices(IServiceCollection services)
{ {
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)
app.UseOcelot().Wait(); {
} app.UseOcelot().Wait();
} }
} }
}

View File

@ -1,40 +1,37 @@
using System; using Microsoft.AspNetCore.Builder;
using CacheManager.Core; using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Builder; using Ocelot.DependencyInjection;
using Microsoft.AspNetCore.Hosting; using Ocelot.Middleware;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; namespace Ocelot.ManualTest
using Microsoft.Extensions.Logging; {
using Ocelot.DependencyInjection; public class ManualTestStartup
using Ocelot.Middleware; {
using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; public void ConfigureServices(IServiceCollection services)
{
namespace Ocelot.ManualTest services.AddAuthentication()
{ .AddJwtBearer("TestKey", x =>
public class ManualTestStartup {
{ x.Authority = "test";
public void ConfigureServices(IServiceCollection services) x.Audience = "test";
{ });
Action<ConfigurationBuilderCachePart> settings = (x) =>
{ services.AddOcelot()
x.WithDictionaryHandle(); .AddCacheManager(x =>
}; {
x.WithDictionaryHandle();
services.AddAuthentication() })
.AddJwtBearer("TestKey", x => .AddOpenTracing(option =>
{ {
x.Authority = "test"; option.CollectorUrl = "http://localhost:9618";
x.Audience = "test"; option.Service = "Ocelot.ManualTest";
}); })
.AddAdministration("/administration", "secret");
services.AddOcelot() }
.AddCacheManager(settings)
.AddAdministration("/administration", "secret"); public void Configure(IApplicationBuilder app)
} {
app.UseOcelot().Wait();
public void Configure(IApplicationBuilder app) }
{ }
app.UseOcelot().Wait(); }
}
}
}

View File

@ -1,287 +1,318 @@
{ {
"ReRoutes": [ "ReRoutes": [
{ {
"DownstreamPathTemplate": "/", "DownstreamPathTemplate": "/api/values",
"DownstreamScheme": "http", "DownstreamScheme": "http",
"DownstreamHostAndPorts": [ "UpstreamPathTemplate": "/api/values",
{ "UpstreamHttpMethod": [ "Get" ],
"Host": "localhost", "DownstreamHostAndPorts": [
"Port": 52876 {
} "Host": "localhost",
], "Port": 5001
"UpstreamPathTemplate": "/identityserverexample", }
"UpstreamHttpMethod": [ "Get" ], ],
"QoSOptions": { "HttpHandlerOptions": {
"ExceptionsAllowedBeforeBreaking": 3, "AllowAutoRedirect": true,
"DurationOfBreak": 10, "UseCookieContainer": true,
"TimeoutValue": 5000 "UseTracing": true
}, }
"AuthenticationOptions": { },
"AuthenticationProviderKey": "TestKey", {
"AllowedScopes": [ "DownstreamPathTemplate": "/",
"openid", "DownstreamScheme": "http",
"offline_access" "DownstreamHostAndPorts": [
] {
}, "Host": "localhost",
"AddHeadersToRequest": { "Port": 52876
"CustomerId": "Claims[CustomerId] > value", }
"LocationId": "Claims[LocationId] > value", ],
"UserType": "Claims[sub] > value[0] > |", "UpstreamPathTemplate": "/identityserverexample",
"UserId": "Claims[sub] > value[1] > |" "UpstreamHttpMethod": [ "Get" ],
}, "QoSOptions": {
"AddClaimsToRequest": { "ExceptionsAllowedBeforeBreaking": 3,
"CustomerId": "Claims[CustomerId] > value", "DurationOfBreak": 10,
"LocationId": "Claims[LocationId] > value", "TimeoutValue": 5000
"UserType": "Claims[sub] > value[0] > |", },
"UserId": "Claims[sub] > value[1] > |" "AuthenticationOptions": {
}, "AuthenticationProviderKey": "TestKey",
"AddQueriesToRequest": { "AllowedScopes": [
"CustomerId": "Claims[CustomerId] > value", "openid",
"LocationId": "Claims[LocationId] > value", "offline_access"
"UserType": "Claims[sub] > value[0] > |", ]
"UserId": "Claims[sub] > value[1] > |" },
}, "AddHeadersToRequest": {
"RouteClaimsRequirement": { "CustomerId": "Claims[CustomerId] > value",
"UserType": "registered" "LocationId": "Claims[LocationId] > value",
}, "UserType": "Claims[sub] > value[0] > |",
"RequestIdKey": "OcRequestId" "UserId": "Claims[sub] > value[1] > |"
}, },
{ "AddClaimsToRequest": {
"DownstreamPathTemplate": "/posts", "CustomerId": "Claims[CustomerId] > value",
"DownstreamScheme": "https", "LocationId": "Claims[LocationId] > value",
"DownstreamHostAndPorts": [ "UserType": "Claims[sub] > value[0] > |",
{ "UserId": "Claims[sub] > value[1] > |"
"Host": "jsonplaceholder.typicode.com", },
"Port": 443 "AddQueriesToRequest": {
} "CustomerId": "Claims[CustomerId] > value",
], "LocationId": "Claims[LocationId] > value",
"UpstreamPathTemplate": "/posts", "UserType": "Claims[sub] > value[0] > |",
"UpstreamHttpMethod": [ "Get" ], "UserId": "Claims[sub] > value[1] > |"
"QoSOptions": { },
"ExceptionsAllowedBeforeBreaking": 3, "RouteClaimsRequirement": {
"DurationOfBreak": 10, "UserType": "registered"
"TimeoutValue": 5000 },
} "RequestIdKey": "OcRequestId"
}, },
{ {
"DownstreamPathTemplate": "/posts/{postId}", "DownstreamPathTemplate": "/posts",
"DownstreamScheme": "http", "DownstreamScheme": "https",
"DownstreamHostAndPorts": [ "DownstreamHostAndPorts": [
{ {
"Host": "jsonplaceholder.typicode.com", "Host": "jsonplaceholder.typicode.com",
"Port": 80 "Port": 443
} }
], ],
"UpstreamPathTemplate": "/posts/{postId}", "UpstreamPathTemplate": "/posts",
"UpstreamHttpMethod": [ "Get" ], "UpstreamHttpMethod": [ "Get" ],
"RequestIdKey": "ReRouteRequestId", "HttpHandlerOptions": {
"QoSOptions": { "AllowAutoRedirect": true,
"ExceptionsAllowedBeforeBreaking": 3, "UseCookieContainer": true
"DurationOfBreak": 10, },
"TimeoutValue": 5000 "QoSOptions": {
} "ExceptionsAllowedBeforeBreaking": 3,
}, "DurationOfBreak": 10,
{ "TimeoutValue": 5000
"DownstreamPathTemplate": "/posts/{postId}/comments", }
"DownstreamScheme": "http", },
"DownstreamHostAndPorts": [ {
{ "DownstreamPathTemplate": "/posts/{postId}",
"Host": "jsonplaceholder.typicode.com", "DownstreamScheme": "http",
"Port": 80 "DownstreamHostAndPorts": [
} {
], "Host": "jsonplaceholder.typicode.com",
"UpstreamPathTemplate": "/posts/{postId}/comments", "Port": 80
"UpstreamHttpMethod": [ "Get" ], }
"QoSOptions": { ],
"ExceptionsAllowedBeforeBreaking": 3, "UpstreamPathTemplate": "/posts/{postId}",
"DurationOfBreak": 10, "UpstreamHttpMethod": [ "Get" ],
"TimeoutValue": 5000 "RequestIdKey": "ReRouteRequestId",
} "HttpHandlerOptions": {
}, "AllowAutoRedirect": true,
{ "UseCookieContainer": true,
"DownstreamPathTemplate": "/comments", "UseTracing": true
"DownstreamScheme": "http", },
"DownstreamHostAndPorts": [ "QoSOptions": {
{ "ExceptionsAllowedBeforeBreaking": 3,
"Host": "jsonplaceholder.typicode.com", "DurationOfBreak": 10,
"Port": 80 "TimeoutValue": 5000
} }
], },
"UpstreamPathTemplate": "/comments", {
"UpstreamHttpMethod": [ "Get" ], "DownstreamPathTemplate": "/posts/{postId}/comments",
"QoSOptions": { "DownstreamScheme": "http",
"ExceptionsAllowedBeforeBreaking": 3, "DownstreamHostAndPorts": [
"DurationOfBreak": 10, {
"TimeoutValue": 5000 "Host": "jsonplaceholder.typicode.com",
} "Port": 80
}, }
{ ],
"DownstreamPathTemplate": "/posts", "UpstreamPathTemplate": "/posts/{postId}/comments",
"DownstreamScheme": "http", "UpstreamHttpMethod": [ "Get" ],
"DownstreamHostAndPorts": [ "HttpHandlerOptions": {
{ "AllowAutoRedirect": true,
"Host": "jsonplaceholder.typicode.com", "UseCookieContainer": true,
"Port": 80 "UseTracing": true
} },
], "QoSOptions": {
"UpstreamPathTemplate": "/posts", "ExceptionsAllowedBeforeBreaking": 3,
"UpstreamHttpMethod": [ "Post" ], "DurationOfBreak": 10,
"QoSOptions": { "TimeoutValue": 5000
"ExceptionsAllowedBeforeBreaking": 3, }
"DurationOfBreak": 10, },
"TimeoutValue": 5000 {
} "DownstreamPathTemplate": "/comments",
}, "DownstreamScheme": "http",
{ "DownstreamHostAndPorts": [
"DownstreamPathTemplate": "/posts/{postId}", {
"DownstreamScheme": "http", "Host": "jsonplaceholder.typicode.com",
"DownstreamHostAndPorts": [ "Port": 80
{ }
"Host": "jsonplaceholder.typicode.com", ],
"Port": 80 "UpstreamPathTemplate": "/comments",
} "UpstreamHttpMethod": [ "Get" ],
], "QoSOptions": {
"UpstreamPathTemplate": "/posts/{postId}", "ExceptionsAllowedBeforeBreaking": 3,
"UpstreamHttpMethod": [ "Put" ], "DurationOfBreak": 10,
"QoSOptions": { "TimeoutValue": 5000
"ExceptionsAllowedBeforeBreaking": 3, }
"DurationOfBreak": 10, },
"TimeoutValue": 5000 {
} "DownstreamPathTemplate": "/posts",
}, "DownstreamScheme": "http",
{ "DownstreamHostAndPorts": [
"DownstreamPathTemplate": "/posts/{postId}", {
"DownstreamScheme": "http", "Host": "jsonplaceholder.typicode.com",
"DownstreamHostAndPorts": [ "Port": 80
{ }
"Host": "jsonplaceholder.typicode.com", ],
"Port": 80 "UpstreamPathTemplate": "/posts",
} "UpstreamHttpMethod": [ "Post" ],
], "QoSOptions": {
"UpstreamPathTemplate": "/posts/{postId}", "ExceptionsAllowedBeforeBreaking": 3,
"UpstreamHttpMethod": [ "Patch" ], "DurationOfBreak": 10,
"QoSOptions": { "TimeoutValue": 5000
"ExceptionsAllowedBeforeBreaking": 3, }
"DurationOfBreak": 10, },
"TimeoutValue": 5000 {
} "DownstreamPathTemplate": "/posts/{postId}",
}, "DownstreamScheme": "http",
{ "DownstreamHostAndPorts": [
"DownstreamPathTemplate": "/posts/{postId}", {
"DownstreamScheme": "http", "Host": "jsonplaceholder.typicode.com",
"DownstreamHostAndPorts": [ "Port": 80
{ }
"Host": "jsonplaceholder.typicode.com", ],
"Port": 80 "UpstreamPathTemplate": "/posts/{postId}",
} "UpstreamHttpMethod": [ "Put" ],
], "QoSOptions": {
"UpstreamPathTemplate": "/posts/{postId}", "ExceptionsAllowedBeforeBreaking": 3,
"UpstreamHttpMethod": [ "Delete" ], "DurationOfBreak": 10,
"QoSOptions": { "TimeoutValue": 5000
"ExceptionsAllowedBeforeBreaking": 3, }
"DurationOfBreak": 10, },
"TimeoutValue": 5000 {
} "DownstreamPathTemplate": "/posts/{postId}",
}, "DownstreamScheme": "http",
{ "DownstreamHostAndPorts": [
"DownstreamPathTemplate": "/api/products", {
"DownstreamScheme": "http", "Host": "jsonplaceholder.typicode.com",
"DownstreamHostAndPorts": [ "Port": 80
{ }
"Host": "jsonplaceholder.typicode.com", ],
"Port": 80 "UpstreamPathTemplate": "/posts/{postId}",
} "UpstreamHttpMethod": [ "Patch" ],
], "QoSOptions": {
"UpstreamPathTemplate": "/products", "ExceptionsAllowedBeforeBreaking": 3,
"UpstreamHttpMethod": [ "Get" ], "DurationOfBreak": 10,
"QoSOptions": { "TimeoutValue": 5000
"ExceptionsAllowedBeforeBreaking": 3, }
"DurationOfBreak": 10, },
"TimeoutValue": 5000 {
}, "DownstreamPathTemplate": "/posts/{postId}",
"FileCacheOptions": { "TtlSeconds": 15 } "DownstreamScheme": "http",
}, "DownstreamHostAndPorts": [
{ {
"DownstreamPathTemplate": "/api/products/{productId}", "Host": "jsonplaceholder.typicode.com",
"DownstreamScheme": "http", "Port": 80
"DownstreamHostAndPorts": [ }
{ ],
"Host": "jsonplaceholder.typicode.com", "UpstreamPathTemplate": "/posts/{postId}",
"Port": 80 "UpstreamHttpMethod": [ "Delete" ],
} "QoSOptions": {
], "ExceptionsAllowedBeforeBreaking": 3,
"UpstreamPathTemplate": "/products/{productId}", "DurationOfBreak": 10,
"UpstreamHttpMethod": [ "Get" ], "TimeoutValue": 5000
"FileCacheOptions": { "TtlSeconds": 15 } }
}, },
{ {
"DownstreamPathTemplate": "/api/products", "DownstreamPathTemplate": "/api/products",
"DownstreamScheme": "http", "DownstreamScheme": "http",
"DownstreamHostAndPorts": [ "DownstreamHostAndPorts": [
{ {
"Host": "jsonplaceholder.typicode.com", "Host": "jsonplaceholder.typicode.com",
"Port": 80 "Port": 80
} }
], ],
"UpstreamPathTemplate": "/products", "UpstreamPathTemplate": "/products",
"UpstreamHttpMethod": [ "Post" ], "UpstreamHttpMethod": [ "Get" ],
"QoSOptions": { "QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, "ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10, "DurationOfBreak": 10,
"TimeoutValue": 5000 "TimeoutValue": 5000
} },
}, "FileCacheOptions": { "TtlSeconds": 15 }
{ },
"DownstreamPathTemplate": "/api/products/{productId}", {
"DownstreamScheme": "http", "DownstreamPathTemplate": "/api/products/{productId}",
"DownstreamHostAndPorts": [ "DownstreamScheme": "http",
{ "DownstreamHostAndPorts": [
"Host": "jsonplaceholder.typicode.com", {
"Port": 80 "Host": "jsonplaceholder.typicode.com",
} "Port": 80
], }
"UpstreamPathTemplate": "/products/{productId}", ],
"UpstreamHttpMethod": [ "Put" ], "UpstreamPathTemplate": "/products/{productId}",
"QoSOptions": { "UpstreamHttpMethod": [ "Get" ],
"ExceptionsAllowedBeforeBreaking": 3, "FileCacheOptions": { "TtlSeconds": 15 }
"DurationOfBreak": 10, },
"TimeoutValue": 5000 {
}, "DownstreamPathTemplate": "/api/products",
"FileCacheOptions": { "TtlSeconds": 15 } "DownstreamScheme": "http",
}, "DownstreamHostAndPorts": [
{ {
"DownstreamPathTemplate": "/posts", "Host": "jsonplaceholder.typicode.com",
"DownstreamScheme": "http", "Port": 80
"DownstreamHostAndPorts": [ }
{ ],
"Host": "jsonplaceholder.typicode.com", "UpstreamPathTemplate": "/products",
"Port": 80 "UpstreamHttpMethod": [ "Post" ],
} "QoSOptions": {
], "ExceptionsAllowedBeforeBreaking": 3,
"UpstreamPathTemplate": "/posts/", "DurationOfBreak": 10,
"UpstreamHttpMethod": [ "Get" ], "TimeoutValue": 5000
"QoSOptions": { }
"ExceptionsAllowedBeforeBreaking": 3, },
"DurationOfBreak": 10, {
"TimeoutValue": 5000 "DownstreamPathTemplate": "/api/products/{productId}",
}, "DownstreamScheme": "http",
"FileCacheOptions": { "TtlSeconds": 15 } "DownstreamHostAndPorts": [
}, {
{ "Host": "jsonplaceholder.typicode.com",
"DownstreamPathTemplate": "/", "Port": 80
"DownstreamScheme": "http", }
"DownstreamHostAndPorts": [ ],
{ "UpstreamPathTemplate": "/products/{productId}",
"Host": "www.bbc.co.uk", "UpstreamHttpMethod": [ "Put" ],
"Port": 80 "QoSOptions": {
} "ExceptionsAllowedBeforeBreaking": 3,
], "DurationOfBreak": 10,
"UpstreamPathTemplate": "/bbc/", "TimeoutValue": 5000
"UpstreamHttpMethod": [ "Get" ] },
} "FileCacheOptions": { "TtlSeconds": 15 }
], },
{
"GlobalConfiguration": { "DownstreamPathTemplate": "/posts",
"RequestIdKey": "OcRequestId" "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"
}
}

View File

@ -0,0 +1,131 @@
using Ocelot.Infrastructure.RequestData;
namespace Ocelot.UnitTests.Cache
{
using System.Linq;
using System.Net;
using System.Net.Http.Headers;
using CacheManager.Core;
using Shouldly;
using System.Collections.Generic;
using System.Net.Http;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Ocelot.Cache;
using Ocelot.Cache.Middleware;
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.DownstreamRouteFinder;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Logging;
using Ocelot.Responses;
using TestStack.BDDfy;
using Xunit;
public class OutputCacheMiddlewareRealCacheTests : ServerHostedMiddlewareTest
{
private IOcelotCache<CachedResponse> _cacheManager;
private CachedResponse _response;
private IRequestScopedDataRepository _repo;
public OutputCacheMiddlewareRealCacheTests()
{
ScopedRepository
.Setup(sr => sr.Get<HttpRequestMessage>("DownstreamRequest"))
.Returns(new OkResponse<HttpRequestMessage>(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123")));
GivenTheTestServerIsConfigured();
}
[Fact]
public void should_cache_content_headers()
{
var content = new StringContent("{\"Test\": 1}")
{
Headers = { ContentType = new MediaTypeHeaderValue("application/json")}
};
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = content,
};
this.Given(x => x.GivenResponseIsNotCached(response))
.And(x => x.GivenTheDownstreamRouteIs())
.And(x => x.GivenThereAreNoErrors())
.And(x => x.GivenThereIsADownstreamUrl())
.When(x => x.WhenICallTheMiddleware())
.Then(x => x.ThenTheContentTypeHeaderIsCached())
.BDDfy();
}
private void ThenTheContentTypeHeaderIsCached()
{
var result = _cacheManager.Get("GET-https://some.url/blah?abcd=123", "kanken");
var header = result.ContentHeaders["Content-Type"];
header.First().ShouldBe("application/json");
}
protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services)
{
var cacheManagerOutputCache = CacheFactory.Build<CachedResponse>("OcelotOutputCache", x =>
{
x.WithDictionaryHandle();
});
_cacheManager = new OcelotCacheManagerCache<CachedResponse>(cacheManagerOutputCache);
services.AddSingleton<ICacheManager<CachedResponse>>(cacheManagerOutputCache);
services.AddSingleton<IOcelotCache<CachedResponse>>(_cacheManager);
services.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
services.AddLogging();
services.AddSingleton(_cacheManager);
services.AddSingleton(ScopedRepository.Object);
services.AddSingleton<IRegionCreator, RegionCreator>();
}
protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app)
{
app.UseOutputCacheMiddleware();
}
private void GivenResponseIsNotCached(HttpResponseMessage message)
{
ScopedRepository
.Setup(x => x.Get<HttpResponseMessage>("HttpResponseMessage"))
.Returns(new OkResponse<HttpResponseMessage>(message));
}
private void GivenTheDownstreamRouteIs()
{
var reRoute = new ReRouteBuilder()
.WithIsCached(true)
.WithCacheOptions(new CacheOptions(100, "kanken"))
.WithUpstreamHttpMethod(new List<string> { "Get" })
.Build();
var downstreamRoute = new DownstreamRoute(new List<PlaceholderNameAndValue>(), reRoute);
ScopedRepository
.Setup(x => x.Get<DownstreamRoute>(It.IsAny<string>()))
.Returns(new OkResponse<DownstreamRoute>(downstreamRoute));
}
private void GivenThereAreNoErrors()
{
ScopedRepository
.Setup(x => x.Get<bool>("OcelotMiddlewareError"))
.Returns(new OkResponse<bool>(false));
}
private void GivenThereIsADownstreamUrl()
{
ScopedRepository
.Setup(x => x.Get<string>("DownstreamUrl"))
.Returns(new OkResponse<string>("anything"));
}
}
}

View File

@ -1,4 +1,6 @@
namespace Ocelot.UnitTests.Cache using System.Net;
namespace Ocelot.UnitTests.Cache
{ {
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -36,7 +38,7 @@
[Fact] [Fact]
public void should_returned_cached_item_when_it_is_in_cache() public void should_returned_cached_item_when_it_is_in_cache()
{ {
var cachedResponse = new CachedResponse(); var cachedResponse = new CachedResponse(HttpStatusCode.OK, new Dictionary<string, IEnumerable<string>>(), "", new Dictionary<string, IEnumerable<string>>());
this.Given(x => x.GivenThereIsACachedResponse(cachedResponse)) this.Given(x => x.GivenThereIsACachedResponse(cachedResponse))
.And(x => x.GivenTheDownstreamRouteIs()) .And(x => x.GivenTheDownstreamRouteIs())
.And(x => x.GivenThereIsADownstreamUrl()) .And(x => x.GivenThereIsADownstreamUrl())

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()
{ {
@ -56,7 +70,7 @@ namespace Ocelot.UnitTests.DependencyInjection
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_set_up_cache_manager() public void should_set_up_cache_manager()
{ {
@ -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

@ -19,10 +19,6 @@
<DebugSymbols>True</DebugSymbols> <DebugSymbols>True</DebugSymbols>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Remove="Responder\HttpContextResponderTests.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Ocelot\Ocelot.csproj" /> <ProjectReference Include="..\..\src\Ocelot\Ocelot.csproj" />
</ItemGroup> </ItemGroup>

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,79 +1,86 @@
namespace Ocelot.UnitTests.Request namespace Ocelot.UnitTests.Request
{ {
using System.Net.Http; using System.Net.Http;
using Ocelot.Request.Builder; using Ocelot.Request.Builder;
using Ocelot.Requester.QoS; using Ocelot.Requester.QoS;
using Ocelot.Responses; using Ocelot.Responses;
using Shouldly; using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
public class HttpRequestCreatorTests public class HttpRequestCreatorTests
{ {
private readonly IRequestCreator _requestCreator; private readonly IRequestCreator _requestCreator;
private readonly bool _isQos; private readonly bool _isQos;
private readonly IQoSProvider _qoSProvider; private readonly IQoSProvider _qoSProvider;
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()
_requestCreator = new HttpRequestCreator(); {
_isQos = true; _requestCreator = new HttpRequestCreator();
_qoSProvider = new NoQoSProvider(); _isQos = true;
_useCookieContainer = false; _qoSProvider = new NoQoSProvider();
_allowAutoRedirect = false; _useCookieContainer = false;
_allowAutoRedirect = false;
_requestMessage = new HttpRequestMessage();
} _requestMessage = new HttpRequestMessage();
}
[Fact]
public void ShouldBuildRequest() [Fact]
{ public void ShouldBuildRequest()
this.When(x => x.WhenIBuildARequest()) {
.Then(x => x.ThenTheRequestContainsTheRequestMessage()) this.When(x => x.WhenIBuildARequest())
.Then(x => x.ThenTheRequestContainsTheIsQos()) .Then(x => x.ThenTheRequestContainsTheRequestMessage())
.Then(x => x.ThenTheRequestContainsTheQosProvider()) .Then(x => x.ThenTheRequestContainsTheIsQos())
.Then(x => x.ThenTheRequestContainsUseCookieContainer()) .Then(x => x.ThenTheRequestContainsTheQosProvider())
.Then(x => x.ThenTheRequestContainsAllowAutoRedirect()) .Then(x => x.ThenTheRequestContainsUseCookieContainer())
.BDDfy(); .Then(x => x.ThenTheRequestContainsUseTracing())
} .Then(x => x.ThenTheRequestContainsAllowAutoRedirect())
.BDDfy();
private void WhenIBuildARequest() }
{
_response = _requestCreator.Build(_requestMessage, private void WhenIBuildARequest()
_isQos, _qoSProvider, _useCookieContainer, _allowAutoRedirect) {
.GetAwaiter() _response = _requestCreator.Build(_requestMessage,
.GetResult(); _isQos, _qoSProvider, _useCookieContainer, _allowAutoRedirect, _reRouteKey, _useTracing)
} .GetAwaiter()
.GetResult();
private void ThenTheRequestContainsTheRequestMessage() }
{
_response.Data.HttpRequestMessage.ShouldBe(_requestMessage); private void ThenTheRequestContainsTheRequestMessage()
} {
_response.Data.HttpRequestMessage.ShouldBe(_requestMessage);
private void ThenTheRequestContainsTheIsQos() }
{
_response.Data.IsQos.ShouldBe(_isQos); private void ThenTheRequestContainsTheIsQos()
} {
_response.Data.IsQos.ShouldBe(_isQos);
private void ThenTheRequestContainsTheQosProvider() }
{
_response.Data.QosProvider.ShouldBe(_qoSProvider); private void ThenTheRequestContainsTheQosProvider()
} {
_response.Data.QosProvider.ShouldBe(_qoSProvider);
}
private void ThenTheRequestContainsUseCookieContainer() private void ThenTheRequestContainsUseCookieContainer()
{ {
_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,81 +1,81 @@
namespace Ocelot.UnitTests.Requester namespace Ocelot.UnitTests.Requester
{ {
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.Logging; using Ocelot.Logging;
using Ocelot.Requester; using Ocelot.Requester;
using Ocelot.Requester.Middleware; using Ocelot.Requester.Middleware;
using Ocelot.Requester.QoS; using Ocelot.Requester.QoS;
using Ocelot.Responses; using Ocelot.Responses;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
public class HttpRequesterMiddlewareTests : ServerHostedMiddlewareTest public class HttpRequesterMiddlewareTests : ServerHostedMiddlewareTest
{ {
private readonly Mock<IHttpRequester> _requester; private readonly Mock<IHttpRequester> _requester;
private OkResponse<HttpResponseMessage> _response; private OkResponse<HttpResponseMessage> _response;
private OkResponse<Ocelot.Request.Request> _request; private OkResponse<Ocelot.Request.Request> _request;
public HttpRequesterMiddlewareTests() public HttpRequesterMiddlewareTests()
{ {
_requester = new Mock<IHttpRequester>(); _requester = new Mock<IHttpRequester>();
GivenTheTestServerIsConfigured(); GivenTheTestServerIsConfigured();
} }
[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())
.Then(x => x.ThenTheScopedRepoIsCalledCorrectly()) .Then(x => x.ThenTheScopedRepoIsCalledCorrectly())
.BDDfy(); .BDDfy();
} }
protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services)
{ {
services.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>(); services.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
services.AddLogging(); services.AddLogging();
services.AddSingleton(_requester.Object); services.AddSingleton(_requester.Object);
services.AddSingleton(ScopedRepository.Object); services.AddSingleton(ScopedRepository.Object);
} }
protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app)
{ {
app.UseHttpRequesterMiddleware(); app.UseHttpRequesterMiddleware();
} }
private void GivenTheRequestIs(Ocelot.Request.Request request) private void GivenTheRequestIs(Ocelot.Request.Request request)
{ {
_request = new OkResponse<Ocelot.Request.Request>(request); _request = new OkResponse<Ocelot.Request.Request>(request);
ScopedRepository ScopedRepository
.Setup(x => x.Get<Ocelot.Request.Request>(It.IsAny<string>())) .Setup(x => x.Get<Ocelot.Request.Request>(It.IsAny<string>()))
.Returns(_request); .Returns(_request);
} }
private void GivenTheRequesterReturns(HttpResponseMessage response) private void GivenTheRequesterReturns(HttpResponseMessage response)
{ {
_response = new OkResponse<HttpResponseMessage>(response); _response = new OkResponse<HttpResponseMessage>(response);
_requester _requester
.Setup(x => x.GetResponse(It.IsAny<Ocelot.Request.Request>())) .Setup(x => x.GetResponse(It.IsAny<Ocelot.Request.Request>()))
.ReturnsAsync(_response); .ReturnsAsync(_response);
} }
private void GivenTheScopedRepoReturns() private void GivenTheScopedRepoReturns()
{ {
ScopedRepository ScopedRepository
.Setup(x => x.Add(It.IsAny<string>(), _response.Data)) .Setup(x => x.Add(It.IsAny<string>(), _response.Data))
.Returns(new OkResponse()); .Returns(new OkResponse());
} }
private void ThenTheScopedRepoIsCalledCorrectly() private void ThenTheScopedRepoIsCalledCorrectly()
{ {
ScopedRepository ScopedRepository
.Verify(x => x.Add("HttpResponseMessage", _response.Data), Times.Once()); .Verify(x => x.Add("HttpResponseMessage", _response.Data), Times.Once());
} }
} }
} }

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

@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using Microsoft.AspNetCore.Http;
using Ocelot.Headers;
using Ocelot.Responder;
using Shouldly;
using Xunit;
namespace Ocelot.UnitTests.Responder
{
public class HttpContextResponderTests
{
private readonly HttpContextResponder _responder;
private RemoveOutputHeaders _removeOutputHeaders;
public HttpContextResponderTests()
{
_removeOutputHeaders = new RemoveOutputHeaders();
_responder = new HttpContextResponder(_removeOutputHeaders);
}
[Fact]
public void should_remove_transfer_encoding_header()
{
var httpContext = new DefaultHttpContext();
var httpResponseMessage = new HttpResponseMessage {Content = new StringContent("")};
httpResponseMessage.Headers.Add("Transfer-Encoding", "woop");
_responder.SetResponseOnHttpContext(httpContext, httpResponseMessage).GetAwaiter().GetResult();
var header = httpContext.Response.Headers["Transfer-Encoding"];
header.ShouldBeEmpty();
}
[Fact]
public void should_have_content_length()
{
var httpContext = new DefaultHttpContext();
var httpResponseMessage = new HttpResponseMessage { Content = new StringContent("test") };
_responder.SetResponseOnHttpContext(httpContext, httpResponseMessage).GetAwaiter().GetResult();
var header = httpContext.Response.Headers["Content-Length"];
header.First().ShouldBe("4");
}
[Fact]
public void should_add_header()
{
var httpContext = new DefaultHttpContext();
var httpResponseMessage = new HttpResponseMessage { Content = new StringContent("test") };
httpResponseMessage.Headers.Add("test", "test");
_responder.SetResponseOnHttpContext(httpContext, httpResponseMessage).GetAwaiter().GetResult();
var header = httpContext.Response.Headers["test"];
header.First().ShouldBe("test");
}
[Fact]
public void should_call_without_exception()
{
var httpContext = new DefaultHttpContext();
_responder.SetErrorResponseOnContext(httpContext, 500);
}
}
}

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]
@ -104,4 +108,4 @@ namespace Ocelot.UnitTests.ServiceDiscovery
_result.ShouldBeOfType<T>(); _result.ShouldBeOfType<T>();
} }
} }
} }