mirror of
				https://github.com/nsnail/Ocelot.git
				synced 2025-11-04 09:15:27 +08:00 
			
		
		
		
	Feat/monorepo (#734)
* copied everything from repos back to ocelot repo * added src projects to sln * removed all test projects that have no tests * added all test projects to sln * removed test not on master * merged unit tests * merged acceptance tests * merged integration tests * fixed namepaces * build script creates packages for all projects * updated docs to make sure no references to external repos that we will remove * +semver: breaking
This commit is contained in:
		
							
								
								
									
										14
									
								
								src/Ocelot.Administration/IIdentityServerConfiguration.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/Ocelot.Administration/IIdentityServerConfiguration.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
namespace Ocelot.Administration
 | 
			
		||||
{
 | 
			
		||||
    using System.Collections.Generic;
 | 
			
		||||
 | 
			
		||||
    public interface IIdentityServerConfiguration
 | 
			
		||||
    {
 | 
			
		||||
        string ApiName { get;  }
 | 
			
		||||
        string ApiSecret { get;  }
 | 
			
		||||
        bool RequireHttps { get;  }
 | 
			
		||||
        List<string> AllowedScopes { get;  }
 | 
			
		||||
        string CredentialsSigningCertificateLocation { get; }
 | 
			
		||||
        string CredentialsSigningCertificatePassword { get; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								src/Ocelot.Administration/IdentityServerConfiguration.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/Ocelot.Administration/IdentityServerConfiguration.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
namespace Ocelot.Administration
 | 
			
		||||
{
 | 
			
		||||
    using System.Collections.Generic;
 | 
			
		||||
 | 
			
		||||
    public class IdentityServerConfiguration : IIdentityServerConfiguration
 | 
			
		||||
    {
 | 
			
		||||
        public IdentityServerConfiguration(
 | 
			
		||||
            string apiName, 
 | 
			
		||||
            bool requireHttps, 
 | 
			
		||||
            string apiSecret,
 | 
			
		||||
            List<string> allowedScopes,
 | 
			
		||||
            string credentialsSigningCertificateLocation, 
 | 
			
		||||
            string credentialsSigningCertificatePassword)
 | 
			
		||||
        {
 | 
			
		||||
            ApiName = apiName;
 | 
			
		||||
            RequireHttps = requireHttps;
 | 
			
		||||
            ApiSecret = apiSecret;
 | 
			
		||||
            AllowedScopes = allowedScopes;
 | 
			
		||||
            CredentialsSigningCertificateLocation = credentialsSigningCertificateLocation;
 | 
			
		||||
            CredentialsSigningCertificatePassword = credentialsSigningCertificatePassword;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string ApiName { get; }
 | 
			
		||||
        public bool RequireHttps { get; }
 | 
			
		||||
        public List<string> AllowedScopes { get; }
 | 
			
		||||
        public string ApiSecret { get; }
 | 
			
		||||
        public string CredentialsSigningCertificateLocation { get; }
 | 
			
		||||
        public string CredentialsSigningCertificatePassword { get; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,23 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Administration
 | 
			
		||||
{
 | 
			
		||||
    public static class IdentityServerConfigurationCreator
 | 
			
		||||
    {
 | 
			
		||||
        public static IdentityServerConfiguration GetIdentityServerConfiguration(string secret)
 | 
			
		||||
        {
 | 
			
		||||
            var credentialsSigningCertificateLocation = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE");
 | 
			
		||||
            var credentialsSigningCertificatePassword = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD");
 | 
			
		||||
 | 
			
		||||
            return new IdentityServerConfiguration(
 | 
			
		||||
                "admin",
 | 
			
		||||
                false,
 | 
			
		||||
                secret,
 | 
			
		||||
                new List<string> { "admin", "openid", "offline_access" },
 | 
			
		||||
                credentialsSigningCertificateLocation,
 | 
			
		||||
                credentialsSigningCertificatePassword
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,38 @@
 | 
			
		||||
namespace Ocelot.Administration
 | 
			
		||||
{
 | 
			
		||||
    using System.Threading.Tasks;
 | 
			
		||||
    using Configuration;
 | 
			
		||||
    using Configuration.Repository;
 | 
			
		||||
    using Microsoft.AspNetCore.Builder;
 | 
			
		||||
    using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
    using Ocelot.Middleware;
 | 
			
		||||
 | 
			
		||||
    public static class IdentityServerMiddlewareConfigurationProvider
 | 
			
		||||
    {
 | 
			
		||||
        public static OcelotMiddlewareConfigurationDelegate Get = builder =>
 | 
			
		||||
        {
 | 
			
		||||
            var internalConfigRepo = builder.ApplicationServices.GetService<IInternalConfigurationRepository>();
 | 
			
		||||
 | 
			
		||||
            var config = internalConfigRepo.Get();
 | 
			
		||||
 | 
			
		||||
            if (!string.IsNullOrEmpty(config.Data.AdministrationPath))
 | 
			
		||||
            {
 | 
			
		||||
                builder.Map(config.Data.AdministrationPath, app =>
 | 
			
		||||
                {
 | 
			
		||||
                    //todo - hack so we know that we are using internal identity server
 | 
			
		||||
                    var identityServerConfiguration = builder.ApplicationServices.GetService<IIdentityServerConfiguration>();
 | 
			
		||||
 | 
			
		||||
                    if (identityServerConfiguration != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        app.UseIdentityServer();
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    app.UseAuthentication();
 | 
			
		||||
                    app.UseMvc();
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								src/Ocelot.Administration/Ocelot.Administration.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/Ocelot.Administration/Ocelot.Administration.csproj
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
<Project Sdk="Microsoft.NET.Sdk">
 | 
			
		||||
  <PropertyGroup>
 | 
			
		||||
    <TargetFramework>netstandard2.0</TargetFramework>
 | 
			
		||||
    <RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
 | 
			
		||||
    <NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
 | 
			
		||||
    <NoPackageAnalysis>true</NoPackageAnalysis>
 | 
			
		||||
    <Description>Provides Ocelot extensions to use the administration API and IdentityService dependencies that come with it</Description>
 | 
			
		||||
    <AssemblyTitle>Ocelot.Administration</AssemblyTitle>
 | 
			
		||||
    <VersionPrefix>0.0.0-dev</VersionPrefix>
 | 
			
		||||
    <AssemblyName>Ocelot.Administration</AssemblyName>
 | 
			
		||||
    <PackageId>Ocelot.Administration</PackageId>
 | 
			
		||||
    <PackageTags>API Gateway;.NET core</PackageTags>
 | 
			
		||||
    <PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Administration</PackageProjectUrl>
 | 
			
		||||
    <PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Administration</PackageProjectUrl>
 | 
			
		||||
    <PackageIconUrl>http://threemammals.com/images/ocelot_logo.png</PackageIconUrl>
 | 
			
		||||
    <RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
 | 
			
		||||
    <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
 | 
			
		||||
    <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
 | 
			
		||||
    <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
 | 
			
		||||
    <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
 | 
			
		||||
    <Authors>Tom Pallister</Authors>
 | 
			
		||||
    <CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
 | 
			
		||||
    <DebugType>full</DebugType>
 | 
			
		||||
    <DebugSymbols>True</DebugSymbols>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <ProjectReference Include="..\Ocelot\Ocelot.csproj" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
 | 
			
		||||
      <PrivateAssets>all</PrivateAssets>
 | 
			
		||||
    </PackageReference>
 | 
			
		||||
    <PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.6.0" />
 | 
			
		||||
    <PackageReference Include="IdentityServer4" Version="2.2.0" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
</Project>
 | 
			
		||||
							
								
								
									
										128
									
								
								src/Ocelot.Administration/OcelotBuilderExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								src/Ocelot.Administration/OcelotBuilderExtensions.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,128 @@
 | 
			
		||||
namespace Ocelot.Administration
 | 
			
		||||
{
 | 
			
		||||
    using System;
 | 
			
		||||
    using System.Collections.Generic;
 | 
			
		||||
    using System.IdentityModel.Tokens.Jwt;
 | 
			
		||||
    using System.Linq;
 | 
			
		||||
    using System.Security.Cryptography.X509Certificates;
 | 
			
		||||
    using Configuration;
 | 
			
		||||
    using Configuration.Creator;
 | 
			
		||||
    using DependencyInjection;
 | 
			
		||||
    using IdentityModel;
 | 
			
		||||
    using IdentityServer4.AccessTokenValidation;
 | 
			
		||||
    using IdentityServer4.Models;
 | 
			
		||||
    using Microsoft.AspNetCore.Builder;
 | 
			
		||||
    using Microsoft.Extensions.Configuration;
 | 
			
		||||
    using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
    using Microsoft.Extensions.DependencyInjection.Extensions;
 | 
			
		||||
    using Ocelot.Middleware;
 | 
			
		||||
 | 
			
		||||
    public static class OcelotBuilderExtensions
 | 
			
		||||
    {
 | 
			
		||||
        public static IOcelotAdministrationBuilder AddAdministration(this IOcelotBuilder builder, string path, string secret)
 | 
			
		||||
        {
 | 
			
		||||
            var administrationPath = new AdministrationPath(path);
 | 
			
		||||
            builder.Services.AddSingleton<OcelotMiddlewareConfigurationDelegate>(IdentityServerMiddlewareConfigurationProvider.Get);
 | 
			
		||||
 | 
			
		||||
            //add identity server for admin area
 | 
			
		||||
            var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(secret);
 | 
			
		||||
 | 
			
		||||
            if (identityServerConfiguration != null)
 | 
			
		||||
            {
 | 
			
		||||
                AddIdentityServer(identityServerConfiguration, administrationPath, builder, builder.Configuration);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            builder.Services.AddSingleton<IAdministrationPath>(administrationPath);
 | 
			
		||||
            return new OcelotAdministrationBuilder(builder.Services, builder.Configuration);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static IOcelotAdministrationBuilder AddAdministration(this IOcelotBuilder builder, string path, Action<IdentityServerAuthenticationOptions> configureOptions)
 | 
			
		||||
        {
 | 
			
		||||
            var administrationPath = new AdministrationPath(path);
 | 
			
		||||
            builder.Services.AddSingleton<OcelotMiddlewareConfigurationDelegate>(IdentityServerMiddlewareConfigurationProvider.Get);
 | 
			
		||||
 | 
			
		||||
            if (configureOptions != null)
 | 
			
		||||
            {
 | 
			
		||||
                AddIdentityServer(configureOptions, builder);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            builder.Services.AddSingleton<IAdministrationPath>(administrationPath);
 | 
			
		||||
            return new OcelotAdministrationBuilder(builder.Services, builder.Configuration);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static void AddIdentityServer(Action<IdentityServerAuthenticationOptions> configOptions, IOcelotBuilder builder)
 | 
			
		||||
        {
 | 
			
		||||
            builder.Services
 | 
			
		||||
                .AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
 | 
			
		||||
                .AddIdentityServerAuthentication(configOptions);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static void AddIdentityServer(IIdentityServerConfiguration identityServerConfiguration, IAdministrationPath adminPath, IOcelotBuilder builder, IConfiguration configuration)
 | 
			
		||||
        {
 | 
			
		||||
            builder.Services.TryAddSingleton<IIdentityServerConfiguration>(identityServerConfiguration);
 | 
			
		||||
            var identityServerBuilder = builder.Services
 | 
			
		||||
                .AddIdentityServer(o => {
 | 
			
		||||
                    o.IssuerUri = "Ocelot";
 | 
			
		||||
                })
 | 
			
		||||
                .AddInMemoryApiResources(Resources(identityServerConfiguration))
 | 
			
		||||
                .AddInMemoryClients(Client(identityServerConfiguration));
 | 
			
		||||
 | 
			
		||||
            var urlFinder = new BaseUrlFinder(configuration);
 | 
			
		||||
            var baseSchemeUrlAndPort = urlFinder.Find();
 | 
			
		||||
            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
 | 
			
		||||
 | 
			
		||||
            builder.Services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
 | 
			
		||||
                .AddIdentityServerAuthentication(o =>
 | 
			
		||||
                {
 | 
			
		||||
                    o.Authority = baseSchemeUrlAndPort + adminPath.Path;
 | 
			
		||||
                    o.ApiName = identityServerConfiguration.ApiName;
 | 
			
		||||
                    o.RequireHttpsMetadata = identityServerConfiguration.RequireHttps;
 | 
			
		||||
                    o.SupportedTokens = SupportedTokens.Both;
 | 
			
		||||
                    o.ApiSecret = identityServerConfiguration.ApiSecret;
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            //todo - refactor naming..
 | 
			
		||||
            if (string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificateLocation) || string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificatePassword))
 | 
			
		||||
            {
 | 
			
		||||
                identityServerBuilder.AddDeveloperSigningCredential();
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                //todo - refactor so calls method?
 | 
			
		||||
                var cert = new X509Certificate2(identityServerConfiguration.CredentialsSigningCertificateLocation, identityServerConfiguration.CredentialsSigningCertificatePassword);
 | 
			
		||||
                identityServerBuilder.AddSigningCredential(cert);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static List<ApiResource> Resources(IIdentityServerConfiguration identityServerConfiguration)
 | 
			
		||||
        {
 | 
			
		||||
            return new List<ApiResource>
 | 
			
		||||
            {
 | 
			
		||||
                new ApiResource(identityServerConfiguration.ApiName, identityServerConfiguration.ApiName)
 | 
			
		||||
                {
 | 
			
		||||
                    ApiSecrets = new List<Secret>
 | 
			
		||||
                    {
 | 
			
		||||
                        new Secret
 | 
			
		||||
                        {
 | 
			
		||||
                            Value = identityServerConfiguration.ApiSecret.Sha256()
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static List<Client> Client(IIdentityServerConfiguration identityServerConfiguration)
 | 
			
		||||
        {
 | 
			
		||||
            return new List<Client>
 | 
			
		||||
            {
 | 
			
		||||
                new Client
 | 
			
		||||
                {
 | 
			
		||||
                    ClientId = identityServerConfiguration.ApiName,
 | 
			
		||||
                    AllowedGrantTypes = GrantTypes.ClientCredentials,
 | 
			
		||||
                    ClientSecrets = new List<Secret> {new Secret(identityServerConfiguration.ApiSecret.Sha256())},
 | 
			
		||||
                    AllowedScopes = { identityServerConfiguration.ApiName }
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								src/Ocelot.Administration/Properties/AssemblyInfo.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/Ocelot.Administration/Properties/AssemblyInfo.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
// General Information about an assembly is controlled through the following
 | 
			
		||||
// set of attributes. Change these attribute values to modify the information
 | 
			
		||||
// associated with an assembly.
 | 
			
		||||
[assembly: AssemblyConfiguration("")]
 | 
			
		||||
[assembly: AssemblyCompany("")]
 | 
			
		||||
[assembly: AssemblyProduct("Ocelot")]
 | 
			
		||||
[assembly: AssemblyTrademark("")]
 | 
			
		||||
 | 
			
		||||
// Setting ComVisible to false makes the types in this assembly not visible
 | 
			
		||||
// to COM components.  If you need to access a type in this assembly from
 | 
			
		||||
// COM, set the ComVisible attribute to true on that type.
 | 
			
		||||
[assembly: ComVisible(false)]
 | 
			
		||||
 | 
			
		||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
 | 
			
		||||
[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")]
 | 
			
		||||
@@ -0,0 +1,38 @@
 | 
			
		||||
<Project Sdk="Microsoft.NET.Sdk">
 | 
			
		||||
  <PropertyGroup>
 | 
			
		||||
    <TargetFramework>netstandard2.0</TargetFramework>
 | 
			
		||||
    <RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
 | 
			
		||||
    <NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
 | 
			
		||||
    <NoPackageAnalysis>true</NoPackageAnalysis>
 | 
			
		||||
    <Description>Provides Ocelot extensions to use CacheManager.Net</Description>
 | 
			
		||||
    <AssemblyTitle>Ocelot.Cache.CacheManager</AssemblyTitle>
 | 
			
		||||
    <VersionPrefix>0.0.0-dev</VersionPrefix>
 | 
			
		||||
    <AssemblyName>Ocelot.Cache.CacheManager</AssemblyName>
 | 
			
		||||
    <PackageId>Ocelot.Cache.CacheManager</PackageId>
 | 
			
		||||
    <PackageTags>API Gateway;.NET core</PackageTags>
 | 
			
		||||
    <PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Cache.CacheManager</PackageProjectUrl>
 | 
			
		||||
    <PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Cache.CacheManager</PackageProjectUrl>
 | 
			
		||||
    <RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
 | 
			
		||||
    <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
 | 
			
		||||
    <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
 | 
			
		||||
    <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
 | 
			
		||||
    <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
 | 
			
		||||
    <Authors>Tom Pallister</Authors>
 | 
			
		||||
    <CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
 | 
			
		||||
    <DebugType>full</DebugType>
 | 
			
		||||
    <DebugSymbols>True</DebugSymbols>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <ProjectReference Include="..\Ocelot\Ocelot.csproj" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
 | 
			
		||||
      <PrivateAssets>all</PrivateAssets>
 | 
			
		||||
    </PackageReference>
 | 
			
		||||
    <PackageReference Include="CacheManager.Core" Version="1.1.2" />
 | 
			
		||||
    <PackageReference Include="CacheManager.Microsoft.Extensions.Configuration" Version="1.1.2" />
 | 
			
		||||
    <PackageReference Include="CacheManager.Microsoft.Extensions.Logging" Version="1.1.2" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
</Project>
 | 
			
		||||
							
								
								
									
										39
									
								
								src/Ocelot.Cache.CacheManager/OcelotBuilderExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/Ocelot.Cache.CacheManager/OcelotBuilderExtensions.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
namespace Ocelot.Cache.CacheManager
 | 
			
		||||
{
 | 
			
		||||
    using System;
 | 
			
		||||
    using Configuration;
 | 
			
		||||
    using Configuration.File;
 | 
			
		||||
    using DependencyInjection;
 | 
			
		||||
    using global::CacheManager.Core;
 | 
			
		||||
    using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
    using Microsoft.Extensions.DependencyInjection.Extensions;
 | 
			
		||||
 | 
			
		||||
    public static class OcelotBuilderExtensions
 | 
			
		||||
    {
 | 
			
		||||
        public static IOcelotBuilder AddCacheManager(this IOcelotBuilder builder, Action<ConfigurationBuilderCachePart> settings)
 | 
			
		||||
        {
 | 
			
		||||
            var cacheManagerOutputCache = CacheFactory.Build<CachedResponse>("OcelotOutputCache", settings);
 | 
			
		||||
            var ocelotOutputCacheManager = new OcelotCacheManagerCache<CachedResponse>(cacheManagerOutputCache);
 | 
			
		||||
 | 
			
		||||
            builder.Services.RemoveAll(typeof(ICacheManager<CachedResponse>));
 | 
			
		||||
            builder.Services.RemoveAll(typeof(IOcelotCache<CachedResponse>));
 | 
			
		||||
            builder.Services.AddSingleton<ICacheManager<CachedResponse>>(cacheManagerOutputCache);
 | 
			
		||||
            builder.Services.AddSingleton<IOcelotCache<CachedResponse>>(ocelotOutputCacheManager);
 | 
			
		||||
 | 
			
		||||
            var ocelotConfigCacheManagerOutputCache = CacheFactory.Build<IInternalConfiguration>("OcelotConfigurationCache", settings);
 | 
			
		||||
            var ocelotConfigCacheManager = new OcelotCacheManagerCache<IInternalConfiguration>(ocelotConfigCacheManagerOutputCache);
 | 
			
		||||
            builder.Services.RemoveAll(typeof(ICacheManager<IInternalConfiguration>));
 | 
			
		||||
            builder.Services.RemoveAll(typeof(IOcelotCache<IInternalConfiguration>));
 | 
			
		||||
            builder.Services.AddSingleton<ICacheManager<IInternalConfiguration>>(ocelotConfigCacheManagerOutputCache);
 | 
			
		||||
            builder.Services.AddSingleton<IOcelotCache<IInternalConfiguration>>(ocelotConfigCacheManager);
 | 
			
		||||
 | 
			
		||||
            var fileConfigCacheManagerOutputCache = CacheFactory.Build<FileConfiguration>("FileConfigurationCache", settings);
 | 
			
		||||
            var fileConfigCacheManager = new OcelotCacheManagerCache<FileConfiguration>(fileConfigCacheManagerOutputCache);
 | 
			
		||||
            builder.Services.RemoveAll(typeof(ICacheManager<FileConfiguration>));
 | 
			
		||||
            builder.Services.RemoveAll(typeof(IOcelotCache<FileConfiguration>));
 | 
			
		||||
            builder.Services.AddSingleton<ICacheManager<FileConfiguration>>(fileConfigCacheManagerOutputCache);
 | 
			
		||||
            builder.Services.AddSingleton<IOcelotCache<FileConfiguration>>(fileConfigCacheManager);
 | 
			
		||||
            return builder;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								src/Ocelot.Cache.CacheManager/OcelotCacheManagerCache.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/Ocelot.Cache.CacheManager/OcelotCacheManagerCache.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
namespace Ocelot.Cache.CacheManager
 | 
			
		||||
{
 | 
			
		||||
    using System;
 | 
			
		||||
    using global::CacheManager.Core;
 | 
			
		||||
 | 
			
		||||
    public class OcelotCacheManagerCache<T> : IOcelotCache<T>
 | 
			
		||||
    {
 | 
			
		||||
        private readonly ICacheManager<T> _cacheManager;
 | 
			
		||||
 | 
			
		||||
        public OcelotCacheManagerCache(ICacheManager<T> cacheManager)
 | 
			
		||||
        {
 | 
			
		||||
            _cacheManager = cacheManager;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Add(string key, T value, TimeSpan ttl, string region)
 | 
			
		||||
        {
 | 
			
		||||
            _cacheManager.Add(new CacheItem<T>(key, region, value, ExpirationMode.Absolute, ttl));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void AddAndDelete(string key, T value, TimeSpan ttl, string region)
 | 
			
		||||
        {
 | 
			
		||||
            var exists = _cacheManager.Get(key);
 | 
			
		||||
 | 
			
		||||
            if (exists != null)
 | 
			
		||||
            {
 | 
			
		||||
                _cacheManager.Remove(key);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Add(key, value, ttl, region);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public T Get(string key, string region)
 | 
			
		||||
        {
 | 
			
		||||
            return _cacheManager.Get<T>(key, region);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void ClearRegion(string region)
 | 
			
		||||
        {
 | 
			
		||||
            _cacheManager.ClearRegion(region);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								src/Ocelot.Cache.CacheManager/Properties/AssemblyInfo.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/Ocelot.Cache.CacheManager/Properties/AssemblyInfo.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
// General Information about an assembly is controlled through the following
 | 
			
		||||
// set of attributes. Change these attribute values to modify the information
 | 
			
		||||
// associated with an assembly.
 | 
			
		||||
[assembly: AssemblyConfiguration("")]
 | 
			
		||||
[assembly: AssemblyCompany("")]
 | 
			
		||||
[assembly: AssemblyProduct("Ocelot")]
 | 
			
		||||
[assembly: AssemblyTrademark("")]
 | 
			
		||||
 | 
			
		||||
// Setting ComVisible to false makes the types in this assembly not visible
 | 
			
		||||
// to COM components.  If you need to access a type in this assembly from
 | 
			
		||||
// COM, set the ComVisible attribute to true on that type.
 | 
			
		||||
[assembly: ComVisible(false)]
 | 
			
		||||
 | 
			
		||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
 | 
			
		||||
[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")]
 | 
			
		||||
							
								
								
									
										76
									
								
								src/Ocelot.Provider.Consul/Consul.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								src/Ocelot.Provider.Consul/Consul.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
			
		||||
namespace Ocelot.Provider.Consul
 | 
			
		||||
{
 | 
			
		||||
    using System;
 | 
			
		||||
    using System.Collections.Generic;
 | 
			
		||||
    using System.Linq;
 | 
			
		||||
    using System.Threading.Tasks;
 | 
			
		||||
    using global::Consul;
 | 
			
		||||
    using Infrastructure.Extensions;
 | 
			
		||||
    using Logging;
 | 
			
		||||
    using ServiceDiscovery.Providers;
 | 
			
		||||
    using Values;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public class Consul : IServiceDiscoveryProvider
 | 
			
		||||
    {
 | 
			
		||||
        private readonly ConsulRegistryConfiguration _config;
 | 
			
		||||
        private readonly IOcelotLogger _logger;
 | 
			
		||||
        private readonly IConsulClient _consul;
 | 
			
		||||
        private const string VersionPrefix = "version-";
 | 
			
		||||
 | 
			
		||||
        public Consul(ConsulRegistryConfiguration config, IOcelotLoggerFactory factory, IConsulClientFactory clientFactory)
 | 
			
		||||
        {
 | 
			
		||||
            _logger = factory.CreateLogger<Consul>();
 | 
			
		||||
            _config = config;
 | 
			
		||||
            _consul = clientFactory.Get(_config);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<List<Service>> Get()
 | 
			
		||||
        {
 | 
			
		||||
            var queryResult = await _consul.Health.Service(_config.KeyOfServiceInConsul, string.Empty, true);
 | 
			
		||||
 | 
			
		||||
            var services = new List<Service>();
 | 
			
		||||
 | 
			
		||||
            foreach (var serviceEntry in queryResult.Response)
 | 
			
		||||
            {
 | 
			
		||||
                if (IsValid(serviceEntry))
 | 
			
		||||
                {
 | 
			
		||||
                    services.Add(BuildService(serviceEntry));
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    _logger.LogWarning($"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();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Service BuildService(ServiceEntry serviceEntry)
 | 
			
		||||
        {
 | 
			
		||||
            return new Service(
 | 
			
		||||
                serviceEntry.Service.Service,
 | 
			
		||||
                new ServiceHostAndPort(serviceEntry.Service.Address, serviceEntry.Service.Port),
 | 
			
		||||
                serviceEntry.Service.ID,
 | 
			
		||||
                GetVersionFromStrings(serviceEntry.Service.Tags),
 | 
			
		||||
                serviceEntry.Service.Tags ?? Enumerable.Empty<string>());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private bool IsValid(ServiceEntry serviceEntry)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrEmpty(serviceEntry.Service.Address) || serviceEntry.Service.Address.Contains("http://") || serviceEntry.Service.Address.Contains("https://") || serviceEntry.Service.Port <= 0)
 | 
			
		||||
            {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private string GetVersionFromStrings(IEnumerable<string> strings)
 | 
			
		||||
        {
 | 
			
		||||
            return strings
 | 
			
		||||
                ?.FirstOrDefault(x => x.StartsWith(VersionPrefix, StringComparison.Ordinal))
 | 
			
		||||
                .TrimStart(VersionPrefix);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								src/Ocelot.Provider.Consul/ConsulClientFactory.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/Ocelot.Provider.Consul/ConsulClientFactory.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
namespace Ocelot.Provider.Consul
 | 
			
		||||
{
 | 
			
		||||
    using System;
 | 
			
		||||
    using global::Consul;
 | 
			
		||||
 | 
			
		||||
    public class ConsulClientFactory : IConsulClientFactory
 | 
			
		||||
    {
 | 
			
		||||
        public IConsulClient Get(ConsulRegistryConfiguration config)
 | 
			
		||||
        {
 | 
			
		||||
            return new ConsulClient(c =>
 | 
			
		||||
            {
 | 
			
		||||
                c.Address = new Uri($"http://{config.Host}:{config.Port}");
 | 
			
		||||
 | 
			
		||||
                if (!string.IsNullOrEmpty(config?.Token))
 | 
			
		||||
                {
 | 
			
		||||
                    c.Token = config.Token;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,96 @@
 | 
			
		||||
namespace Ocelot.Provider.Consul
 | 
			
		||||
{
 | 
			
		||||
    using System;
 | 
			
		||||
    using System.Text;
 | 
			
		||||
    using System.Threading.Tasks;
 | 
			
		||||
    using Configuration.File;
 | 
			
		||||
    using Configuration.Repository;
 | 
			
		||||
    using global::Consul;
 | 
			
		||||
    using Logging;
 | 
			
		||||
    using Newtonsoft.Json;
 | 
			
		||||
    using Responses;
 | 
			
		||||
 | 
			
		||||
    public class ConsulFileConfigurationRepository : IFileConfigurationRepository
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IConsulClient _consul;
 | 
			
		||||
        private readonly string _configurationKey;
 | 
			
		||||
        private readonly Cache.IOcelotCache<FileConfiguration> _cache;
 | 
			
		||||
        private readonly IOcelotLogger _logger;
 | 
			
		||||
 | 
			
		||||
        public ConsulFileConfigurationRepository(
 | 
			
		||||
            Cache.IOcelotCache<FileConfiguration> cache,
 | 
			
		||||
            IInternalConfigurationRepository repo,
 | 
			
		||||
            IConsulClientFactory factory,
 | 
			
		||||
            IOcelotLoggerFactory loggerFactory)
 | 
			
		||||
        {
 | 
			
		||||
            _logger = loggerFactory.CreateLogger<ConsulFileConfigurationRepository>();
 | 
			
		||||
            _cache = cache;
 | 
			
		||||
 | 
			
		||||
            var internalConfig = repo.Get();
 | 
			
		||||
 | 
			
		||||
            _configurationKey = "InternalConfiguration";
 | 
			
		||||
 | 
			
		||||
            string token = null;
 | 
			
		||||
 | 
			
		||||
            if (!internalConfig.IsError)
 | 
			
		||||
            {
 | 
			
		||||
                token = internalConfig.Data.ServiceProviderConfiguration.Token;
 | 
			
		||||
                _configurationKey = !string.IsNullOrEmpty(internalConfig.Data.ServiceProviderConfiguration.ConfigurationKey) ?
 | 
			
		||||
                    internalConfig.Data.ServiceProviderConfiguration.ConfigurationKey : _configurationKey;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var config = new ConsulRegistryConfiguration(internalConfig.Data.ServiceProviderConfiguration.Host,
 | 
			
		||||
                internalConfig.Data.ServiceProviderConfiguration.Port, _configurationKey, token);
 | 
			
		||||
 | 
			
		||||
            _consul = factory.Get(config);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<Response<FileConfiguration>> Get()
 | 
			
		||||
        {
 | 
			
		||||
            var config = _cache.Get(_configurationKey, _configurationKey);
 | 
			
		||||
 | 
			
		||||
            if (config != null)
 | 
			
		||||
            {
 | 
			
		||||
                return new OkResponse<FileConfiguration>(config);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var queryResult = await _consul.KV.Get(_configurationKey);
 | 
			
		||||
 | 
			
		||||
            if (queryResult.Response == null)
 | 
			
		||||
            {
 | 
			
		||||
                return new OkResponse<FileConfiguration>(null);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var bytes = queryResult.Response.Value;
 | 
			
		||||
 | 
			
		||||
            var json = Encoding.UTF8.GetString(bytes);
 | 
			
		||||
 | 
			
		||||
            var consulConfig = JsonConvert.DeserializeObject<FileConfiguration>(json);
 | 
			
		||||
 | 
			
		||||
            return new OkResponse<FileConfiguration>(consulConfig);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<Response> Set(FileConfiguration ocelotConfiguration)
 | 
			
		||||
        {
 | 
			
		||||
            var json = JsonConvert.SerializeObject(ocelotConfiguration, Formatting.Indented);
 | 
			
		||||
 | 
			
		||||
            var bytes = Encoding.UTF8.GetBytes(json);
 | 
			
		||||
 | 
			
		||||
            var kvPair = new KVPair(_configurationKey)
 | 
			
		||||
            {
 | 
			
		||||
                Value = bytes
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var result = await _consul.KV.Put(kvPair);
 | 
			
		||||
 | 
			
		||||
            if (result.Response)
 | 
			
		||||
            {
 | 
			
		||||
                _cache.AddAndDelete(_configurationKey, ocelotConfiguration, TimeSpan.FromSeconds(3), _configurationKey);
 | 
			
		||||
 | 
			
		||||
                return new OkResponse();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return new ErrorResponse(new UnableToSetConfigInConsulError($"Unable to set FileConfiguration in consul, response status code from consul was {result.StatusCode}"));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,93 @@
 | 
			
		||||
namespace Ocelot.Provider.Consul
 | 
			
		||||
{
 | 
			
		||||
    using System;
 | 
			
		||||
    using System.Linq;
 | 
			
		||||
    using System.Threading.Tasks;
 | 
			
		||||
    using Configuration.Creator;
 | 
			
		||||
    using Configuration.File;
 | 
			
		||||
    using Configuration.Repository;
 | 
			
		||||
    using Microsoft.AspNetCore.Builder;
 | 
			
		||||
    using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
    using Microsoft.Extensions.Options;
 | 
			
		||||
    using Middleware;
 | 
			
		||||
    using Responses;
 | 
			
		||||
 | 
			
		||||
    public static class ConsulMiddlewareConfigurationProvider
 | 
			
		||||
    {
 | 
			
		||||
        public static OcelotMiddlewareConfigurationDelegate Get = async builder =>
 | 
			
		||||
        {
 | 
			
		||||
            var fileConfigRepo = builder.ApplicationServices.GetService<IFileConfigurationRepository>();
 | 
			
		||||
            var fileConfig = builder.ApplicationServices.GetService<IOptionsMonitor<FileConfiguration>>();
 | 
			
		||||
            var internalConfigCreator = builder.ApplicationServices.GetService<IInternalConfigurationCreator>();
 | 
			
		||||
            var internalConfigRepo = builder.ApplicationServices.GetService<IInternalConfigurationRepository>();
 | 
			
		||||
 | 
			
		||||
            if (UsingConsul(fileConfigRepo))
 | 
			
		||||
            {
 | 
			
		||||
                await SetFileConfigInConsul(builder, fileConfigRepo, fileConfig, internalConfigCreator, internalConfigRepo);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        private static bool UsingConsul(IFileConfigurationRepository fileConfigRepo)
 | 
			
		||||
        {
 | 
			
		||||
            return fileConfigRepo.GetType() == typeof(ConsulFileConfigurationRepository);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static async Task SetFileConfigInConsul(IApplicationBuilder builder,
 | 
			
		||||
            IFileConfigurationRepository fileConfigRepo, IOptionsMonitor<FileConfiguration> fileConfig,
 | 
			
		||||
            IInternalConfigurationCreator internalConfigCreator, IInternalConfigurationRepository internalConfigRepo)
 | 
			
		||||
        {
 | 
			
		||||
            // get the config from consul.
 | 
			
		||||
            var fileConfigFromConsul = await fileConfigRepo.Get();
 | 
			
		||||
 | 
			
		||||
            if (IsError(fileConfigFromConsul))
 | 
			
		||||
            {
 | 
			
		||||
                ThrowToStopOcelotStarting(fileConfigFromConsul);
 | 
			
		||||
            }
 | 
			
		||||
            else if (ConfigNotStoredInConsul(fileConfigFromConsul))
 | 
			
		||||
            {
 | 
			
		||||
                //there was no config in consul set the file in config in consul
 | 
			
		||||
                await fileConfigRepo.Set(fileConfig.CurrentValue);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                // create the internal config from consul data
 | 
			
		||||
                var internalConfig = await internalConfigCreator.Create(fileConfigFromConsul.Data);
 | 
			
		||||
 | 
			
		||||
                if (IsError(internalConfig))
 | 
			
		||||
                {
 | 
			
		||||
                    ThrowToStopOcelotStarting(internalConfig);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    // add the internal config to the internal repo
 | 
			
		||||
                    var response = internalConfigRepo.AddOrReplace(internalConfig.Data);
 | 
			
		||||
 | 
			
		||||
                    if (IsError(response))
 | 
			
		||||
                    {
 | 
			
		||||
                        ThrowToStopOcelotStarting(response);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (IsError(internalConfig))
 | 
			
		||||
                {
 | 
			
		||||
                    ThrowToStopOcelotStarting(internalConfig);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static void ThrowToStopOcelotStarting(Response config)
 | 
			
		||||
        {
 | 
			
		||||
            throw new Exception($"Unable to start Ocelot, errors are: {string.Join(",", config.Errors.Select(x => x.ToString()))}");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static bool IsError(Response response)
 | 
			
		||||
        {
 | 
			
		||||
            return response == null || response.IsError;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static bool ConfigNotStoredInConsul(Response<FileConfiguration> fileConfigFromConsul)
 | 
			
		||||
        {
 | 
			
		||||
            return fileConfigFromConsul.Data == null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								src/Ocelot.Provider.Consul/ConsulProviderFactory.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/Ocelot.Provider.Consul/ConsulProviderFactory.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
namespace Ocelot.Provider.Consul
 | 
			
		||||
{
 | 
			
		||||
    using System.Threading.Tasks;
 | 
			
		||||
    using Logging;
 | 
			
		||||
    using Microsoft.AspNetCore.Builder;
 | 
			
		||||
    using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
    using ServiceDiscovery;
 | 
			
		||||
 | 
			
		||||
    public static class ConsulProviderFactory
 | 
			
		||||
    {
 | 
			
		||||
        public static ServiceDiscoveryFinderDelegate Get = (provider, config, name) =>
 | 
			
		||||
        {
 | 
			
		||||
            var factory = provider.GetService<IOcelotLoggerFactory>();
 | 
			
		||||
 | 
			
		||||
            var consulFactory = provider.GetService<IConsulClientFactory>();
 | 
			
		||||
 | 
			
		||||
            var consulRegistryConfiguration = new ConsulRegistryConfiguration(config.Host, config.Port, name, config.Token);
 | 
			
		||||
 | 
			
		||||
            var consulServiceDiscoveryProvider = new Consul(consulRegistryConfiguration, factory, consulFactory);
 | 
			
		||||
 | 
			
		||||
            if (config.Type?.ToLower() == "pollconsul")
 | 
			
		||||
            {
 | 
			
		||||
                return new PollConsul(config.PollingInterval, factory, consulServiceDiscoveryProvider);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return consulServiceDiscoveryProvider;
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								src/Ocelot.Provider.Consul/ConsulRegistryConfiguration.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/Ocelot.Provider.Consul/ConsulRegistryConfiguration.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
namespace Ocelot.Provider.Consul
 | 
			
		||||
{
 | 
			
		||||
    public class ConsulRegistryConfiguration
 | 
			
		||||
    {
 | 
			
		||||
        public ConsulRegistryConfiguration(string host, int port, string keyOfServiceInConsul, string token)
 | 
			
		||||
        {
 | 
			
		||||
            Host = string.IsNullOrEmpty(host) ? "localhost" : host;
 | 
			
		||||
            Port = port > 0 ? port : 8500;
 | 
			
		||||
            KeyOfServiceInConsul = keyOfServiceInConsul;
 | 
			
		||||
            Token = token;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string KeyOfServiceInConsul { get; }
 | 
			
		||||
        public string Host { get; }
 | 
			
		||||
        public int Port { get; }
 | 
			
		||||
        public string Token { get; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								src/Ocelot.Provider.Consul/IConsulClientFactory.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/Ocelot.Provider.Consul/IConsulClientFactory.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
namespace Ocelot.Provider.Consul
 | 
			
		||||
{
 | 
			
		||||
    using global::Consul;
 | 
			
		||||
 | 
			
		||||
    public interface IConsulClientFactory
 | 
			
		||||
    {
 | 
			
		||||
        IConsulClient Get(ConsulRegistryConfiguration config);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								src/Ocelot.Provider.Consul/Ocelot.Provider.Consul.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/Ocelot.Provider.Consul/Ocelot.Provider.Consul.csproj
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
<Project Sdk="Microsoft.NET.Sdk">
 | 
			
		||||
  <PropertyGroup>
 | 
			
		||||
    <TargetFramework>netstandard2.0</TargetFramework>
 | 
			
		||||
    <RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
 | 
			
		||||
    <NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
 | 
			
		||||
    <NoPackageAnalysis>true</NoPackageAnalysis>
 | 
			
		||||
    <Description>Provides Ocelot extensions to use Consul</Description>
 | 
			
		||||
    <AssemblyTitle>Ocelot.Provider.Consul</AssemblyTitle>
 | 
			
		||||
    <VersionPrefix>0.0.0-dev</VersionPrefix>
 | 
			
		||||
    <AssemblyName>Ocelot.Provider.Consul</AssemblyName>
 | 
			
		||||
    <PackageId>Ocelot.Provider.Consul</PackageId>
 | 
			
		||||
    <PackageTags>API Gateway;.NET core</PackageTags>
 | 
			
		||||
    <PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Provider.Consul</PackageProjectUrl>
 | 
			
		||||
    <PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Provider.Consul</PackageProjectUrl>
 | 
			
		||||
    <PackageIconUrl>http://threemammals.com/images/ocelot_logo.png</PackageIconUrl>
 | 
			
		||||
    <RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
 | 
			
		||||
    <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
 | 
			
		||||
    <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
 | 
			
		||||
    <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
 | 
			
		||||
    <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
 | 
			
		||||
    <Authors>Tom Pallister</Authors>
 | 
			
		||||
    <CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
 | 
			
		||||
    <DebugType>full</DebugType>
 | 
			
		||||
    <DebugSymbols>True</DebugSymbols>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <ProjectReference Include="..\Ocelot\Ocelot.csproj" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <PackageReference Include="Consul" Version="0.7.2.6" />
 | 
			
		||||
    <PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
 | 
			
		||||
      <PrivateAssets>all</PrivateAssets>
 | 
			
		||||
    </PackageReference>
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
</Project>
 | 
			
		||||
							
								
								
									
										26
									
								
								src/Ocelot.Provider.Consul/OcelotBuilderExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/Ocelot.Provider.Consul/OcelotBuilderExtensions.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
namespace Ocelot.Provider.Consul
 | 
			
		||||
{
 | 
			
		||||
    using Configuration.Repository;
 | 
			
		||||
    using DependencyInjection;
 | 
			
		||||
    using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
    using Middleware;
 | 
			
		||||
    using ServiceDiscovery;
 | 
			
		||||
 | 
			
		||||
    public static class OcelotBuilderExtensions
 | 
			
		||||
    {
 | 
			
		||||
        public static IOcelotBuilder AddConsul(this IOcelotBuilder builder)
 | 
			
		||||
        {
 | 
			
		||||
            builder.Services.AddSingleton<ServiceDiscoveryFinderDelegate>(ConsulProviderFactory.Get);
 | 
			
		||||
            builder.Services.AddSingleton<IConsulClientFactory, ConsulClientFactory>();
 | 
			
		||||
            return builder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static IOcelotBuilder AddConfigStoredInConsul(this IOcelotBuilder builder)
 | 
			
		||||
        {
 | 
			
		||||
            builder.Services.AddSingleton<OcelotMiddlewareConfigurationDelegate>(ConsulMiddlewareConfigurationProvider.Get);
 | 
			
		||||
            builder.Services.AddHostedService<FileConfigurationPoller>();
 | 
			
		||||
            builder.Services.AddSingleton<IFileConfigurationRepository, ConsulFileConfigurationRepository>();
 | 
			
		||||
            return builder;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,47 @@
 | 
			
		||||
namespace Ocelot.Provider.Consul
 | 
			
		||||
{
 | 
			
		||||
    using System.Collections.Generic;
 | 
			
		||||
    using System.Threading;
 | 
			
		||||
    using System.Threading.Tasks;
 | 
			
		||||
    using Logging;
 | 
			
		||||
    using ServiceDiscovery.Providers;
 | 
			
		||||
    using Values;
 | 
			
		||||
 | 
			
		||||
    public class PollConsul : IServiceDiscoveryProvider
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IOcelotLogger _logger;
 | 
			
		||||
        private readonly IServiceDiscoveryProvider _consulServiceDiscoveryProvider;
 | 
			
		||||
        private readonly Timer _timer;
 | 
			
		||||
        private bool _polling;
 | 
			
		||||
        private List<Service> _services;
 | 
			
		||||
 | 
			
		||||
        public PollConsul(int pollingInterval, IOcelotLoggerFactory factory, IServiceDiscoveryProvider consulServiceDiscoveryProvider)
 | 
			
		||||
        {
 | 
			
		||||
            _logger = factory.CreateLogger<PollConsul>();
 | 
			
		||||
            _consulServiceDiscoveryProvider = consulServiceDiscoveryProvider;
 | 
			
		||||
            _services = new List<Service>();
 | 
			
		||||
 | 
			
		||||
            _timer = new Timer(async x =>
 | 
			
		||||
            {
 | 
			
		||||
                if (_polling)
 | 
			
		||||
                {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                _polling = true;
 | 
			
		||||
                await Poll();
 | 
			
		||||
                _polling = false;
 | 
			
		||||
            }, null, pollingInterval, pollingInterval);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Task<List<Service>> Get()
 | 
			
		||||
        {
 | 
			
		||||
            return Task.FromResult(_services);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task Poll()
 | 
			
		||||
        {
 | 
			
		||||
            _services = await _consulServiceDiscoveryProvider.Get();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								src/Ocelot.Provider.Consul/Properties/AssemblyInfo.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/Ocelot.Provider.Consul/Properties/AssemblyInfo.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
// General Information about an assembly is controlled through the following
 | 
			
		||||
// set of attributes. Change these attribute values to modify the information
 | 
			
		||||
// associated with an assembly.
 | 
			
		||||
[assembly: AssemblyConfiguration("")]
 | 
			
		||||
[assembly: AssemblyCompany("")]
 | 
			
		||||
[assembly: AssemblyProduct("Ocelot")]
 | 
			
		||||
[assembly: AssemblyTrademark("")]
 | 
			
		||||
 | 
			
		||||
// Setting ComVisible to false makes the types in this assembly not visible
 | 
			
		||||
// to COM components.  If you need to access a type in this assembly from
 | 
			
		||||
// COM, set the ComVisible attribute to true on that type.
 | 
			
		||||
[assembly: ComVisible(false)]
 | 
			
		||||
 | 
			
		||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
 | 
			
		||||
[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")]
 | 
			
		||||
							
								
								
									
										12
									
								
								src/Ocelot.Provider.Consul/UnableToSetConfigInConsulError.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/Ocelot.Provider.Consul/UnableToSetConfigInConsulError.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
namespace Ocelot.Provider.Consul
 | 
			
		||||
{
 | 
			
		||||
    using Errors;
 | 
			
		||||
 | 
			
		||||
    public class UnableToSetConfigInConsulError : Error
 | 
			
		||||
    {
 | 
			
		||||
        public UnableToSetConfigInConsulError(string s) 
 | 
			
		||||
            : base(s, OcelotErrorCode.UnknownError)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								src/Ocelot.Provider.Eureka/Eureka.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/Ocelot.Provider.Eureka/Eureka.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
namespace Ocelot.Provider.Eureka
 | 
			
		||||
{
 | 
			
		||||
    using System.Collections.Generic;
 | 
			
		||||
    using System.Linq;
 | 
			
		||||
    using System.Threading.Tasks;
 | 
			
		||||
    using ServiceDiscovery.Providers;
 | 
			
		||||
    using Steeltoe.Common.Discovery;
 | 
			
		||||
    using Values;
 | 
			
		||||
 | 
			
		||||
    public class Eureka : IServiceDiscoveryProvider
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IDiscoveryClient _client;
 | 
			
		||||
        private readonly string _serviceName;
 | 
			
		||||
 | 
			
		||||
        public Eureka(string serviceName, IDiscoveryClient client)
 | 
			
		||||
        {
 | 
			
		||||
            _client = client;
 | 
			
		||||
            _serviceName = serviceName;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Task<List<Service>> Get()
 | 
			
		||||
        {
 | 
			
		||||
            var services = new List<Service>();
 | 
			
		||||
 | 
			
		||||
            var instances = _client.GetInstances(_serviceName);
 | 
			
		||||
 | 
			
		||||
            if (instances != null && instances.Any())
 | 
			
		||||
            {
 | 
			
		||||
                services.AddRange(instances.Select(i => new Service(i.ServiceId, new ServiceHostAndPort(i.Host, i.Port), "", "", new List<string>())));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Task.FromResult(services);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,31 @@
 | 
			
		||||
namespace Ocelot.Provider.Eureka
 | 
			
		||||
{
 | 
			
		||||
    using System.Threading.Tasks;
 | 
			
		||||
    using Configuration;
 | 
			
		||||
    using Configuration.Repository;
 | 
			
		||||
    using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
    using Middleware;
 | 
			
		||||
    using Pivotal.Discovery.Client;
 | 
			
		||||
 | 
			
		||||
    public class EurekaMiddlewareConfigurationProvider
 | 
			
		||||
    {
 | 
			
		||||
        public static OcelotMiddlewareConfigurationDelegate Get = builder =>
 | 
			
		||||
        {
 | 
			
		||||
            var internalConfigRepo = builder.ApplicationServices.GetService<IInternalConfigurationRepository>();
 | 
			
		||||
 | 
			
		||||
            var config = internalConfigRepo.Get();
 | 
			
		||||
 | 
			
		||||
            if (UsingEurekaServiceDiscoveryProvider(config.Data))
 | 
			
		||||
            {
 | 
			
		||||
                builder.UseDiscoveryClient();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        private static bool UsingEurekaServiceDiscoveryProvider(IInternalConfiguration configuration)
 | 
			
		||||
        {
 | 
			
		||||
            return configuration?.ServiceProviderConfiguration != null && configuration.ServiceProviderConfiguration.Type?.ToLower() == "eureka";
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								src/Ocelot.Provider.Eureka/EurekaProviderFactory.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/Ocelot.Provider.Eureka/EurekaProviderFactory.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
namespace Ocelot.Provider.Eureka
 | 
			
		||||
{
 | 
			
		||||
    using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
    using ServiceDiscovery;
 | 
			
		||||
    using ServiceDiscovery.Providers;
 | 
			
		||||
    using Steeltoe.Common.Discovery;
 | 
			
		||||
 | 
			
		||||
    public static class EurekaProviderFactory
 | 
			
		||||
    {
 | 
			
		||||
        public static ServiceDiscoveryFinderDelegate Get = (provider, config, name) =>
 | 
			
		||||
        {
 | 
			
		||||
            var client = provider.GetService<IDiscoveryClient>();
 | 
			
		||||
 | 
			
		||||
            if (config.Type?.ToLower() == "eureka" && client != null)
 | 
			
		||||
            {
 | 
			
		||||
                return new Eureka(name, client);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return null;
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								src/Ocelot.Provider.Eureka/Ocelot.Provider.Eureka.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/Ocelot.Provider.Eureka/Ocelot.Provider.Eureka.csproj
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
<Project Sdk="Microsoft.NET.Sdk">
 | 
			
		||||
  <PropertyGroup>
 | 
			
		||||
    <TargetFramework>netstandard2.0</TargetFramework>
 | 
			
		||||
    <RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
 | 
			
		||||
    <NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
 | 
			
		||||
    <NoPackageAnalysis>true</NoPackageAnalysis>
 | 
			
		||||
    <Description>Provides Ocelot extensions to use Eureka</Description>
 | 
			
		||||
    <AssemblyTitle>Ocelot.Provider.Eureka</AssemblyTitle>
 | 
			
		||||
    <VersionPrefix>0.0.0-dev</VersionPrefix>
 | 
			
		||||
    <AssemblyName>Ocelot.Provider.Eureka</AssemblyName>
 | 
			
		||||
    <PackageId>Ocelot.Provider.Eureka</PackageId>
 | 
			
		||||
    <PackageTags>API Gateway;.NET core</PackageTags>
 | 
			
		||||
    <PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Provider.Eureka</PackageProjectUrl>
 | 
			
		||||
    <PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Provider.Eureka</PackageProjectUrl>
 | 
			
		||||
    <PackageIconUrl>http://threemammals.com/images/ocelot_logo.png</PackageIconUrl>
 | 
			
		||||
    <RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
 | 
			
		||||
    <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
 | 
			
		||||
    <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
 | 
			
		||||
    <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
 | 
			
		||||
    <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
 | 
			
		||||
    <Authors>Tom Pallister</Authors>
 | 
			
		||||
    <CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
 | 
			
		||||
    <DebugType>full</DebugType>
 | 
			
		||||
    <DebugSymbols>True</DebugSymbols>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <ProjectReference Include="..\Ocelot\Ocelot.csproj" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <PackageReference Include="Pivotal.Discovery.ClientCore" Version="2.0.1" />
 | 
			
		||||
    <PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
 | 
			
		||||
      <PrivateAssets>all</PrivateAssets>
 | 
			
		||||
    </PackageReference>
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
</Project>
 | 
			
		||||
							
								
								
									
										23
									
								
								src/Ocelot.Provider.Eureka/OcelotBuilderExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/Ocelot.Provider.Eureka/OcelotBuilderExtensions.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
namespace Ocelot.Provider.Eureka
 | 
			
		||||
{
 | 
			
		||||
    using System.Linq;
 | 
			
		||||
    using DependencyInjection;
 | 
			
		||||
    using Microsoft.Extensions.Configuration;
 | 
			
		||||
    using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
    using Middleware;
 | 
			
		||||
    using Pivotal.Discovery.Client;
 | 
			
		||||
    using ServiceDiscovery;
 | 
			
		||||
 | 
			
		||||
    public static class OcelotBuilderExtensions
 | 
			
		||||
    {
 | 
			
		||||
        public static IOcelotBuilder AddEureka(this IOcelotBuilder builder)
 | 
			
		||||
        {
 | 
			
		||||
            var service = builder.Services.First(x => x.ServiceType == typeof(IConfiguration));
 | 
			
		||||
            var configuration = (IConfiguration)service.ImplementationInstance;
 | 
			
		||||
            builder.Services.AddDiscoveryClient(configuration);
 | 
			
		||||
            builder.Services.AddSingleton<ServiceDiscoveryFinderDelegate>(EurekaProviderFactory.Get);
 | 
			
		||||
            builder.Services.AddSingleton<OcelotMiddlewareConfigurationDelegate>(EurekaMiddlewareConfigurationProvider.Get);
 | 
			
		||||
            return builder;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								src/Ocelot.Provider.Eureka/Properties/AssemblyInfo.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/Ocelot.Provider.Eureka/Properties/AssemblyInfo.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
// General Information about an assembly is controlled through the following
 | 
			
		||||
// set of attributes. Change these attribute values to modify the information
 | 
			
		||||
// associated with an assembly.
 | 
			
		||||
[assembly: AssemblyConfiguration("")]
 | 
			
		||||
[assembly: AssemblyCompany("")]
 | 
			
		||||
[assembly: AssemblyProduct("Ocelot")]
 | 
			
		||||
[assembly: AssemblyTrademark("")]
 | 
			
		||||
 | 
			
		||||
// Setting ComVisible to false makes the types in this assembly not visible
 | 
			
		||||
// to COM components.  If you need to access a type in this assembly from
 | 
			
		||||
// COM, set the ComVisible attribute to true on that type.
 | 
			
		||||
[assembly: ComVisible(false)]
 | 
			
		||||
 | 
			
		||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
 | 
			
		||||
[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")]
 | 
			
		||||
							
								
								
									
										17
									
								
								src/Ocelot.Provider.Polly/CircuitBreaker.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/Ocelot.Provider.Polly/CircuitBreaker.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
using Polly.CircuitBreaker;
 | 
			
		||||
using Polly.Timeout;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Provider.Polly
 | 
			
		||||
{
 | 
			
		||||
    public class CircuitBreaker
 | 
			
		||||
    {
 | 
			
		||||
        public CircuitBreaker(CircuitBreakerPolicy circuitBreakerPolicy, TimeoutPolicy timeoutPolicy)
 | 
			
		||||
        {
 | 
			
		||||
            CircuitBreakerPolicy = circuitBreakerPolicy;
 | 
			
		||||
            TimeoutPolicy = timeoutPolicy;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public CircuitBreakerPolicy CircuitBreakerPolicy { get; private set; }
 | 
			
		||||
        public TimeoutPolicy TimeoutPolicy { get; private set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
<Project Sdk="Microsoft.NET.Sdk">
 | 
			
		||||
  <PropertyGroup>
 | 
			
		||||
    <TargetFramework>netstandard2.0</TargetFramework>
 | 
			
		||||
    <RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
 | 
			
		||||
    <NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
 | 
			
		||||
    <NoPackageAnalysis>true</NoPackageAnalysis>
 | 
			
		||||
    <Description>Provides Ocelot extensions to use Polly.NET</Description>
 | 
			
		||||
    <AssemblyTitle>Ocelot.Provider.Polly</AssemblyTitle>
 | 
			
		||||
    <VersionPrefix>0.0.0-dev</VersionPrefix>
 | 
			
		||||
    <AssemblyName>Ocelot.Provider.Polly</AssemblyName>
 | 
			
		||||
    <PackageId>Ocelot.Provider.Polly</PackageId>
 | 
			
		||||
    <PackageTags>API Gateway;.NET core</PackageTags>
 | 
			
		||||
    <PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Provider.Polly</PackageProjectUrl>
 | 
			
		||||
    <PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Provider.Polly</PackageProjectUrl>
 | 
			
		||||
    <PackageIconUrl>http://threemammals.com/images/ocelot_logo.png</PackageIconUrl>
 | 
			
		||||
    <RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
 | 
			
		||||
    <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
 | 
			
		||||
    <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
 | 
			
		||||
    <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
 | 
			
		||||
    <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
 | 
			
		||||
    <Authors>Tom Pallister</Authors>
 | 
			
		||||
    <CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
 | 
			
		||||
    <DebugType>full</DebugType>
 | 
			
		||||
    <DebugSymbols>True</DebugSymbols>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <ProjectReference Include="..\Ocelot\Ocelot.csproj" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
 | 
			
		||||
      <PrivateAssets>all</PrivateAssets>
 | 
			
		||||
    </PackageReference>
 | 
			
		||||
    <PackageReference Include="Polly" Version="6.0.1" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
</Project>
 | 
			
		||||
							
								
								
									
										38
									
								
								src/Ocelot.Provider.Polly/OcelotBuilderExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/Ocelot.Provider.Polly/OcelotBuilderExtensions.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
namespace Ocelot.Provider.Polly
 | 
			
		||||
{
 | 
			
		||||
    using System;
 | 
			
		||||
    using System.Collections.Generic;
 | 
			
		||||
    using System.Net.Http;
 | 
			
		||||
    using System.Threading.Tasks;
 | 
			
		||||
    using Configuration;
 | 
			
		||||
    using DependencyInjection;
 | 
			
		||||
    using Errors;
 | 
			
		||||
    using global::Polly.CircuitBreaker;
 | 
			
		||||
    using global::Polly.Timeout;
 | 
			
		||||
    using Logging;
 | 
			
		||||
    using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
    using Requester;
 | 
			
		||||
 | 
			
		||||
    public static class OcelotBuilderExtensions
 | 
			
		||||
    {
 | 
			
		||||
        public static IOcelotBuilder AddPolly(this IOcelotBuilder builder)
 | 
			
		||||
        {
 | 
			
		||||
            var errorMapping = new Dictionary<Type, Func<Exception, Error>>
 | 
			
		||||
            {
 | 
			
		||||
                {typeof(TaskCanceledException), e => new RequestTimedOutError(e)},
 | 
			
		||||
                {typeof(TimeoutRejectedException), e => new RequestTimedOutError(e)},
 | 
			
		||||
                {typeof(BrokenCircuitException), e => new RequestTimedOutError(e)}
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            builder.Services.AddSingleton(errorMapping);
 | 
			
		||||
 | 
			
		||||
            DelegatingHandler QosDelegatingHandlerDelegate(DownstreamReRoute reRoute, IOcelotLoggerFactory logger)
 | 
			
		||||
            {
 | 
			
		||||
                return new PollyCircuitBreakingDelegatingHandler(new PollyQoSProvider(reRoute, logger), logger);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            builder.Services.AddSingleton((QosDelegatingHandlerDelegate) QosDelegatingHandlerDelegate);
 | 
			
		||||
            return builder;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,43 @@
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Ocelot.Logging;
 | 
			
		||||
using Polly;
 | 
			
		||||
using Polly.CircuitBreaker;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Provider.Polly
 | 
			
		||||
{
 | 
			
		||||
    public class PollyCircuitBreakingDelegatingHandler : DelegatingHandler
 | 
			
		||||
    {
 | 
			
		||||
        private readonly PollyQoSProvider _qoSProvider;
 | 
			
		||||
        private readonly IOcelotLogger _logger;
 | 
			
		||||
 | 
			
		||||
        public PollyCircuitBreakingDelegatingHandler(
 | 
			
		||||
            PollyQoSProvider qoSProvider,
 | 
			
		||||
            IOcelotLoggerFactory loggerFactory)
 | 
			
		||||
        {
 | 
			
		||||
            _qoSProvider = qoSProvider;
 | 
			
		||||
            _logger = loggerFactory.CreateLogger<PollyCircuitBreakingDelegatingHandler>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                return await Policy
 | 
			
		||||
                    .WrapAsync(_qoSProvider.CircuitBreaker.CircuitBreakerPolicy, _qoSProvider.CircuitBreaker.TimeoutPolicy)
 | 
			
		||||
                    .ExecuteAsync(() => base.SendAsync(request,cancellationToken));
 | 
			
		||||
            }
 | 
			
		||||
            catch (BrokenCircuitException ex)
 | 
			
		||||
            {
 | 
			
		||||
                _logger.LogError($"Reached to allowed number of exceptions. Circuit is open",ex);
 | 
			
		||||
                throw;
 | 
			
		||||
            }
 | 
			
		||||
            catch (HttpRequestException ex)
 | 
			
		||||
            {
 | 
			
		||||
                _logger.LogError($"Error in CircuitBreakingDelegatingHandler.SendAync", ex);
 | 
			
		||||
                throw;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										52
									
								
								src/Ocelot.Provider.Polly/PollyQoSProvider.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/Ocelot.Provider.Polly/PollyQoSProvider.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
namespace Ocelot.Provider.Polly
 | 
			
		||||
{
 | 
			
		||||
    using System;
 | 
			
		||||
    using System.Net.Http;
 | 
			
		||||
    using global::Polly;
 | 
			
		||||
    using global::Polly.CircuitBreaker;
 | 
			
		||||
    using global::Polly.Timeout;
 | 
			
		||||
    using Ocelot.Configuration;
 | 
			
		||||
    using Ocelot.Logging;
 | 
			
		||||
 | 
			
		||||
    public class PollyQoSProvider
 | 
			
		||||
    {
 | 
			
		||||
        private readonly CircuitBreakerPolicy _circuitBreakerPolicy;
 | 
			
		||||
        private readonly TimeoutPolicy _timeoutPolicy;
 | 
			
		||||
        private readonly IOcelotLogger _logger;
 | 
			
		||||
 | 
			
		||||
        public PollyQoSProvider(DownstreamReRoute reRoute, IOcelotLoggerFactory loggerFactory)
 | 
			
		||||
        {
 | 
			
		||||
            _logger = loggerFactory.CreateLogger<PollyQoSProvider>();
 | 
			
		||||
 | 
			
		||||
            Enum.TryParse(reRoute.QosOptions.TimeoutStrategy, out TimeoutStrategy strategy);
 | 
			
		||||
 | 
			
		||||
            _timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromMilliseconds(reRoute.QosOptions.TimeoutValue), strategy);
 | 
			
		||||
 | 
			
		||||
            _circuitBreakerPolicy = Policy
 | 
			
		||||
                .Handle<HttpRequestException>()
 | 
			
		||||
                .Or<TimeoutRejectedException>()
 | 
			
		||||
                .Or<TimeoutException>()
 | 
			
		||||
                .CircuitBreakerAsync(
 | 
			
		||||
                    exceptionsAllowedBeforeBreaking: reRoute.QosOptions.ExceptionsAllowedBeforeBreaking,
 | 
			
		||||
                    durationOfBreak: TimeSpan.FromMilliseconds(reRoute.QosOptions.DurationOfBreak),
 | 
			
		||||
                    onBreak: (ex, breakDelay) =>
 | 
			
		||||
                    {
 | 
			
		||||
                        _logger.LogError(
 | 
			
		||||
                            ".Breaker logging: Breaking the circuit for " + breakDelay.TotalMilliseconds + "ms!", ex);
 | 
			
		||||
                    },
 | 
			
		||||
                    onReset: () =>
 | 
			
		||||
                    {
 | 
			
		||||
                        _logger.LogDebug(".Breaker logging: Call ok! Closed the circuit again.");
 | 
			
		||||
                    },
 | 
			
		||||
                    onHalfOpen: () =>
 | 
			
		||||
                    {
 | 
			
		||||
                        _logger.LogDebug(".Breaker logging: Half-open; next call is a trial.");
 | 
			
		||||
                    }
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
            CircuitBreaker = new CircuitBreaker(_circuitBreakerPolicy, _timeoutPolicy);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public CircuitBreaker CircuitBreaker { get; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								src/Ocelot.Provider.Polly/Properties/AssemblyInfo.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/Ocelot.Provider.Polly/Properties/AssemblyInfo.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
// General Information about an assembly is controlled through the following
 | 
			
		||||
// set of attributes. Change these attribute values to modify the information
 | 
			
		||||
// associated with an assembly.
 | 
			
		||||
[assembly: AssemblyConfiguration("")]
 | 
			
		||||
[assembly: AssemblyCompany("")]
 | 
			
		||||
[assembly: AssemblyProduct("Ocelot")]
 | 
			
		||||
[assembly: AssemblyTrademark("")]
 | 
			
		||||
 | 
			
		||||
// Setting ComVisible to false makes the types in this assembly not visible
 | 
			
		||||
// to COM components.  If you need to access a type in this assembly from
 | 
			
		||||
// COM, set the ComVisible attribute to true on that type.
 | 
			
		||||
[assembly: ComVisible(false)]
 | 
			
		||||
 | 
			
		||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
 | 
			
		||||
[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")]
 | 
			
		||||
							
								
								
									
										13
									
								
								src/Ocelot.Provider.Polly/RequestTimedOutError.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/Ocelot.Provider.Polly/RequestTimedOutError.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
namespace Ocelot.Provider.Polly
 | 
			
		||||
{
 | 
			
		||||
    using System;
 | 
			
		||||
    using Errors;
 | 
			
		||||
 | 
			
		||||
    public class RequestTimedOutError : Error
 | 
			
		||||
    {
 | 
			
		||||
        public RequestTimedOutError(Exception exception)
 | 
			
		||||
            : base($"Timeout making http request, exception: {exception}", OcelotErrorCode.RequestTimedOutError)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								src/Ocelot.Provider.Rafty/BearerToken.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/Ocelot.Provider.Rafty/BearerToken.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
namespace Ocelot.Provider.Rafty
 | 
			
		||||
{
 | 
			
		||||
    using Newtonsoft.Json;
 | 
			
		||||
 | 
			
		||||
    internal class BearerToken
 | 
			
		||||
    {
 | 
			
		||||
        [JsonProperty("access_token")]
 | 
			
		||||
        public string AccessToken { get; set; }
 | 
			
		||||
 | 
			
		||||
        [JsonProperty("expires_in")]
 | 
			
		||||
        public int ExpiresIn { get; set; }
 | 
			
		||||
 | 
			
		||||
        [JsonProperty("token_type")]
 | 
			
		||||
        public string TokenType { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								src/Ocelot.Provider.Rafty/FakeCommand.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/Ocelot.Provider.Rafty/FakeCommand.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
namespace Ocelot.Provider.Rafty
 | 
			
		||||
{
 | 
			
		||||
    using global::Rafty.FiniteStateMachine;
 | 
			
		||||
 | 
			
		||||
    public class FakeCommand : ICommand
 | 
			
		||||
    {
 | 
			
		||||
        public FakeCommand(string value)
 | 
			
		||||
        {
 | 
			
		||||
            this.Value = value;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string Value { get; private set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								src/Ocelot.Provider.Rafty/FilePeer.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/Ocelot.Provider.Rafty/FilePeer.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
namespace Ocelot.Provider.Rafty
 | 
			
		||||
{
 | 
			
		||||
    public class FilePeer
 | 
			
		||||
    {
 | 
			
		||||
        public string HostAndPort { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								src/Ocelot.Provider.Rafty/FilePeers.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/Ocelot.Provider.Rafty/FilePeers.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
namespace Ocelot.Provider.Rafty
 | 
			
		||||
{
 | 
			
		||||
    using System.Collections.Generic;
 | 
			
		||||
 | 
			
		||||
    public class FilePeers
 | 
			
		||||
    {
 | 
			
		||||
        public FilePeers()
 | 
			
		||||
        {
 | 
			
		||||
            Peers = new List<FilePeer>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public List<FilePeer> Peers { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										45
									
								
								src/Ocelot.Provider.Rafty/FilePeersProvider.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/Ocelot.Provider.Rafty/FilePeersProvider.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
namespace Ocelot.Provider.Rafty
 | 
			
		||||
{
 | 
			
		||||
    using System.Net.Http;
 | 
			
		||||
    using Configuration;
 | 
			
		||||
    using Configuration.Repository;
 | 
			
		||||
    using global::Rafty.Concensus.Peers;
 | 
			
		||||
    using global::Rafty.Infrastructure;
 | 
			
		||||
    using Microsoft.Extensions.Options;
 | 
			
		||||
    using Middleware;
 | 
			
		||||
    using System.Collections.Generic;
 | 
			
		||||
    using Administration;
 | 
			
		||||
 | 
			
		||||
    public class FilePeersProvider : IPeersProvider
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IOptions<FilePeers> _options;
 | 
			
		||||
        private readonly List<IPeer> _peers;
 | 
			
		||||
        private IBaseUrlFinder _finder;
 | 
			
		||||
        private IInternalConfigurationRepository _repo;
 | 
			
		||||
        private IIdentityServerConfiguration _identityServerConfig;
 | 
			
		||||
 | 
			
		||||
        public FilePeersProvider(IOptions<FilePeers> options, IBaseUrlFinder finder, IInternalConfigurationRepository repo, IIdentityServerConfiguration identityServerConfig)
 | 
			
		||||
        {
 | 
			
		||||
            _identityServerConfig = identityServerConfig;
 | 
			
		||||
            _repo = repo;
 | 
			
		||||
            _finder = finder;
 | 
			
		||||
            _options = options;
 | 
			
		||||
            _peers = new List<IPeer>();
 | 
			
		||||
 | 
			
		||||
            var config = _repo.Get();
 | 
			
		||||
            foreach (var item in _options.Value.Peers)
 | 
			
		||||
            {
 | 
			
		||||
                var httpClient = new HttpClient();
 | 
			
		||||
 | 
			
		||||
                //todo what if this errors?
 | 
			
		||||
                var httpPeer = new HttpPeer(item.HostAndPort, httpClient, _finder, config.Data, _identityServerConfig);
 | 
			
		||||
                _peers.Add(httpPeer);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public List<IPeer> Get()
 | 
			
		||||
        {
 | 
			
		||||
            return _peers;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										130
									
								
								src/Ocelot.Provider.Rafty/HttpPeer.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								src/Ocelot.Provider.Rafty/HttpPeer.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,130 @@
 | 
			
		||||
namespace Ocelot.Provider.Rafty
 | 
			
		||||
{
 | 
			
		||||
    using System.Net.Http;
 | 
			
		||||
    using System.Threading.Tasks;
 | 
			
		||||
    using Configuration;
 | 
			
		||||
    using global::Rafty.Concensus.Messages;
 | 
			
		||||
    using global::Rafty.Concensus.Peers;
 | 
			
		||||
    using global::Rafty.FiniteStateMachine;
 | 
			
		||||
    using global::Rafty.Infrastructure;
 | 
			
		||||
    using Middleware;
 | 
			
		||||
    using Newtonsoft.Json;
 | 
			
		||||
    using System;
 | 
			
		||||
    using System.Collections.Generic;
 | 
			
		||||
    using Administration;
 | 
			
		||||
 | 
			
		||||
    public class HttpPeer : IPeer
 | 
			
		||||
    {
 | 
			
		||||
        private readonly string _hostAndPort;
 | 
			
		||||
        private readonly HttpClient _httpClient;
 | 
			
		||||
        private readonly JsonSerializerSettings _jsonSerializerSettings;
 | 
			
		||||
        private readonly string _baseSchemeUrlAndPort;
 | 
			
		||||
        private BearerToken _token;
 | 
			
		||||
        private readonly IInternalConfiguration _config;
 | 
			
		||||
        private readonly IIdentityServerConfiguration _identityServerConfiguration;
 | 
			
		||||
 | 
			
		||||
        public HttpPeer(string hostAndPort, HttpClient httpClient, IBaseUrlFinder finder, IInternalConfiguration config, IIdentityServerConfiguration identityServerConfiguration)
 | 
			
		||||
        {
 | 
			
		||||
            _identityServerConfiguration = identityServerConfiguration;
 | 
			
		||||
            _config = config;
 | 
			
		||||
            Id = hostAndPort;
 | 
			
		||||
            _hostAndPort = hostAndPort;
 | 
			
		||||
            _httpClient = httpClient;
 | 
			
		||||
            _jsonSerializerSettings = new JsonSerializerSettings()
 | 
			
		||||
            {
 | 
			
		||||
                TypeNameHandling = TypeNameHandling.All
 | 
			
		||||
            };
 | 
			
		||||
            _baseSchemeUrlAndPort = finder.Find();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string Id { get; }
 | 
			
		||||
 | 
			
		||||
        public async Task<RequestVoteResponse> Request(RequestVote requestVote)
 | 
			
		||||
        {
 | 
			
		||||
            if (_token == null)
 | 
			
		||||
            {
 | 
			
		||||
                await SetToken();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var json = JsonConvert.SerializeObject(requestVote, _jsonSerializerSettings);
 | 
			
		||||
            var content = new StringContent(json);
 | 
			
		||||
            content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
 | 
			
		||||
            var response = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/requestvote", content);
 | 
			
		||||
            if (response.IsSuccessStatusCode)
 | 
			
		||||
            {
 | 
			
		||||
                return JsonConvert.DeserializeObject<RequestVoteResponse>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return new RequestVoteResponse(false, requestVote.Term);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<AppendEntriesResponse> Request(AppendEntries appendEntries)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                if (_token == null)
 | 
			
		||||
                {
 | 
			
		||||
                    await SetToken();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var json = JsonConvert.SerializeObject(appendEntries, _jsonSerializerSettings);
 | 
			
		||||
                var content = new StringContent(json);
 | 
			
		||||
                content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
 | 
			
		||||
                var response = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/appendEntries", content);
 | 
			
		||||
                if (response.IsSuccessStatusCode)
 | 
			
		||||
                {
 | 
			
		||||
                    return JsonConvert.DeserializeObject<AppendEntriesResponse>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return new AppendEntriesResponse(appendEntries.Term, false);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                Console.WriteLine(ex);
 | 
			
		||||
                return new AppendEntriesResponse(appendEntries.Term, false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<Response<T>> Request<T>(T command)
 | 
			
		||||
            where T : ICommand
 | 
			
		||||
        {
 | 
			
		||||
            Console.WriteLine("SENDING REQUEST....");
 | 
			
		||||
            if (_token == null)
 | 
			
		||||
            {
 | 
			
		||||
                await SetToken();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var json = JsonConvert.SerializeObject(command, _jsonSerializerSettings);
 | 
			
		||||
            var content = new StringContent(json);
 | 
			
		||||
            content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
 | 
			
		||||
            var response = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/command", content);
 | 
			
		||||
            if (response.IsSuccessStatusCode)
 | 
			
		||||
            {
 | 
			
		||||
                Console.WriteLine("REQUEST OK....");
 | 
			
		||||
                var okResponse = JsonConvert.DeserializeObject<OkResponse<ICommand>>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
 | 
			
		||||
                return new OkResponse<T>((T)okResponse.Command);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Console.WriteLine("REQUEST NOT OK....");
 | 
			
		||||
            return new ErrorResponse<T>(await response.Content.ReadAsStringAsync(), command);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task SetToken()
 | 
			
		||||
        {
 | 
			
		||||
            var tokenUrl = $"{_baseSchemeUrlAndPort}{_config.AdministrationPath}/connect/token";
 | 
			
		||||
            var formData = new List<KeyValuePair<string, string>>
 | 
			
		||||
            {
 | 
			
		||||
                new KeyValuePair<string, string>("client_id", _identityServerConfiguration.ApiName),
 | 
			
		||||
                new KeyValuePair<string, string>("client_secret", _identityServerConfiguration.ApiSecret),
 | 
			
		||||
                new KeyValuePair<string, string>("scope", _identityServerConfiguration.ApiName),
 | 
			
		||||
                new KeyValuePair<string, string>("grant_type", "client_credentials")
 | 
			
		||||
            };
 | 
			
		||||
            var content = new FormUrlEncodedContent(formData);
 | 
			
		||||
            var response = await _httpClient.PostAsync(tokenUrl, content);
 | 
			
		||||
            var responseContent = await response.Content.ReadAsStringAsync();
 | 
			
		||||
            response.EnsureSuccessStatusCode();
 | 
			
		||||
            _token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
 | 
			
		||||
            _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(_token.TokenType, _token.AccessToken);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								src/Ocelot.Provider.Rafty/Ocelot.Provider.Rafty.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/Ocelot.Provider.Rafty/Ocelot.Provider.Rafty.csproj
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
<Project Sdk="Microsoft.NET.Sdk">
 | 
			
		||||
  <PropertyGroup>
 | 
			
		||||
    <TargetFramework>netstandard2.0</TargetFramework>
 | 
			
		||||
    <RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
 | 
			
		||||
    <NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
 | 
			
		||||
    <NoPackageAnalysis>true</NoPackageAnalysis>
 | 
			
		||||
    <Description>Provides Ocelot extensions to use Rafty</Description>
 | 
			
		||||
    <AssemblyTitle>Ocelot.Provider.Rafty</AssemblyTitle>
 | 
			
		||||
    <VersionPrefix>0.0.0-dev</VersionPrefix>
 | 
			
		||||
    <AssemblyName>Ocelot.Provider.Rafty</AssemblyName>
 | 
			
		||||
    <PackageId>Ocelot.Provider.Rafty</PackageId>
 | 
			
		||||
    <PackageTags>API Gateway;.NET core</PackageTags>
 | 
			
		||||
    <PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Provider.Rafty</PackageProjectUrl>
 | 
			
		||||
    <PackageProjectUrl>https://github.com/ThreeMammals/Ocelot.Provider.Rafty</PackageProjectUrl>
 | 
			
		||||
    <PackageIconUrl>http://threemammals.com/images/ocelot_logo.png</PackageIconUrl>
 | 
			
		||||
    <RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
 | 
			
		||||
    <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
 | 
			
		||||
    <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
 | 
			
		||||
    <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
 | 
			
		||||
    <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
 | 
			
		||||
    <Authors>Tom Pallister</Authors>
 | 
			
		||||
    <CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
 | 
			
		||||
    <DebugType>full</DebugType>
 | 
			
		||||
    <DebugSymbols>True</DebugSymbols>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <ProjectReference Include="..\Ocelot\Ocelot.csproj" />
 | 
			
		||||
    <ProjectReference Include="..\Ocelot.Administration\Ocelot.Administration.csproj" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <PackageReference Include="Microsoft.Data.SQLite" Version="2.2.0"/>
 | 
			
		||||
    <PackageReference Include="Rafty" Version="0.4.4"/>
 | 
			
		||||
    <PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
 | 
			
		||||
      <PrivateAssets>all</PrivateAssets>
 | 
			
		||||
    </PackageReference>
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
</Project>
 | 
			
		||||
@@ -0,0 +1,28 @@
 | 
			
		||||
namespace Ocelot.Provider.Rafty
 | 
			
		||||
{
 | 
			
		||||
    using Configuration.Setter;
 | 
			
		||||
    using DependencyInjection;
 | 
			
		||||
    using global::Rafty.Concensus.Node;
 | 
			
		||||
    using global::Rafty.FiniteStateMachine;
 | 
			
		||||
    using global::Rafty.Infrastructure;
 | 
			
		||||
    using global::Rafty.Log;
 | 
			
		||||
    using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
    using Microsoft.Extensions.DependencyInjection.Extensions;
 | 
			
		||||
 | 
			
		||||
    public static class OcelotAdministrationBuilderExtensions
 | 
			
		||||
    {
 | 
			
		||||
        public static IOcelotAdministrationBuilder AddRafty(this IOcelotAdministrationBuilder builder)
 | 
			
		||||
        {
 | 
			
		||||
            var settings = new InMemorySettings(4000, 6000, 100, 10000);
 | 
			
		||||
            builder.Services.RemoveAll<IFileConfigurationSetter>();
 | 
			
		||||
            builder.Services.AddSingleton<IFileConfigurationSetter, RaftyFileConfigurationSetter>();
 | 
			
		||||
            builder.Services.AddSingleton<ILog, SqlLiteLog>();
 | 
			
		||||
            builder.Services.AddSingleton<IFiniteStateMachine, OcelotFiniteStateMachine>();
 | 
			
		||||
            builder.Services.AddSingleton<ISettings>(settings);
 | 
			
		||||
            builder.Services.AddSingleton<IPeersProvider, FilePeersProvider>();
 | 
			
		||||
            builder.Services.AddSingleton<INode, Node>();
 | 
			
		||||
            builder.Services.Configure<FilePeers>(builder.ConfigurationRoot);
 | 
			
		||||
            return builder;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								src/Ocelot.Provider.Rafty/OcelotFiniteStateMachine.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/Ocelot.Provider.Rafty/OcelotFiniteStateMachine.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
namespace Ocelot.Provider.Rafty
 | 
			
		||||
{
 | 
			
		||||
    using System.Threading.Tasks;
 | 
			
		||||
    using Configuration.Setter;
 | 
			
		||||
    using global::Rafty.FiniteStateMachine;
 | 
			
		||||
    using global::Rafty.Log;
 | 
			
		||||
 | 
			
		||||
    public class OcelotFiniteStateMachine : IFiniteStateMachine
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IFileConfigurationSetter _setter;
 | 
			
		||||
 | 
			
		||||
        public OcelotFiniteStateMachine(IFileConfigurationSetter setter)
 | 
			
		||||
        {
 | 
			
		||||
            _setter = setter;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task Handle(LogEntry log)
 | 
			
		||||
        {
 | 
			
		||||
            //todo - handle an error
 | 
			
		||||
            //hack it to just cast as at the moment we know this is the only command :P
 | 
			
		||||
            var hack = (UpdateFileConfiguration)log.CommandData;
 | 
			
		||||
            await _setter.Set(hack.Configuration);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								src/Ocelot.Provider.Rafty/Properties/AssemblyInfo.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/Ocelot.Provider.Rafty/Properties/AssemblyInfo.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
// General Information about an assembly is controlled through the following
 | 
			
		||||
// set of attributes. Change these attribute values to modify the information
 | 
			
		||||
// associated with an assembly.
 | 
			
		||||
[assembly: AssemblyConfiguration("")]
 | 
			
		||||
[assembly: AssemblyCompany("")]
 | 
			
		||||
[assembly: AssemblyProduct("Ocelot")]
 | 
			
		||||
[assembly: AssemblyTrademark("")]
 | 
			
		||||
 | 
			
		||||
// Setting ComVisible to false makes the types in this assembly not visible
 | 
			
		||||
// to COM components.  If you need to access a type in this assembly from
 | 
			
		||||
// COM, set the ComVisible attribute to true on that type.
 | 
			
		||||
[assembly: ComVisible(false)]
 | 
			
		||||
 | 
			
		||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
 | 
			
		||||
[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")]
 | 
			
		||||
							
								
								
									
										96
									
								
								src/Ocelot.Provider.Rafty/RaftController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								src/Ocelot.Provider.Rafty/RaftController.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,96 @@
 | 
			
		||||
namespace Ocelot.Provider.Rafty
 | 
			
		||||
{
 | 
			
		||||
    using System;
 | 
			
		||||
    using System.IO;
 | 
			
		||||
    using System.Threading.Tasks;
 | 
			
		||||
    using global::Rafty.Concensus.Messages;
 | 
			
		||||
    using global::Rafty.Concensus.Node;
 | 
			
		||||
    using global::Rafty.FiniteStateMachine;
 | 
			
		||||
    using Logging;
 | 
			
		||||
    using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
    using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
    using Middleware;
 | 
			
		||||
    using Newtonsoft.Json;
 | 
			
		||||
 | 
			
		||||
    [Authorize]
 | 
			
		||||
    [Route("raft")]
 | 
			
		||||
    public class RaftController : Controller
 | 
			
		||||
    {
 | 
			
		||||
        private readonly INode _node;
 | 
			
		||||
        private readonly IOcelotLogger _logger;
 | 
			
		||||
        private readonly string _baseSchemeUrlAndPort;
 | 
			
		||||
        private readonly JsonSerializerSettings _jsonSerialiserSettings;
 | 
			
		||||
 | 
			
		||||
        public RaftController(INode node, IOcelotLoggerFactory loggerFactory, IBaseUrlFinder finder)
 | 
			
		||||
        {
 | 
			
		||||
            _jsonSerialiserSettings = new JsonSerializerSettings
 | 
			
		||||
            {
 | 
			
		||||
                TypeNameHandling = TypeNameHandling.All
 | 
			
		||||
            };
 | 
			
		||||
            _baseSchemeUrlAndPort = finder.Find();
 | 
			
		||||
            _logger = loggerFactory.CreateLogger<RaftController>();
 | 
			
		||||
            _node = node;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Route("appendentries")]
 | 
			
		||||
        public async Task<IActionResult> AppendEntries()
 | 
			
		||||
        {
 | 
			
		||||
            using (var reader = new StreamReader(HttpContext.Request.Body))
 | 
			
		||||
            {
 | 
			
		||||
                var json = await reader.ReadToEndAsync();
 | 
			
		||||
 | 
			
		||||
                var appendEntries = JsonConvert.DeserializeObject<AppendEntries>(json, _jsonSerialiserSettings);
 | 
			
		||||
 | 
			
		||||
                _logger.LogDebug($"{_baseSchemeUrlAndPort}/appendentries called, my state is {_node.State.GetType().FullName}");
 | 
			
		||||
 | 
			
		||||
                var appendEntriesResponse = await _node.Handle(appendEntries);
 | 
			
		||||
 | 
			
		||||
                return new OkObjectResult(appendEntriesResponse);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Route("requestvote")]
 | 
			
		||||
        public async Task<IActionResult> RequestVote()
 | 
			
		||||
        {
 | 
			
		||||
            using (var reader = new StreamReader(HttpContext.Request.Body))
 | 
			
		||||
            {
 | 
			
		||||
                var json = await reader.ReadToEndAsync();
 | 
			
		||||
 | 
			
		||||
                var requestVote = JsonConvert.DeserializeObject<RequestVote>(json, _jsonSerialiserSettings);
 | 
			
		||||
 | 
			
		||||
                _logger.LogDebug($"{_baseSchemeUrlAndPort}/requestvote called, my state is {_node.State.GetType().FullName}");
 | 
			
		||||
 | 
			
		||||
                var requestVoteResponse = await _node.Handle(requestVote);
 | 
			
		||||
 | 
			
		||||
                return new OkObjectResult(requestVoteResponse);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Route("command")]
 | 
			
		||||
        public async Task<IActionResult> Command()
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                using (var reader = new StreamReader(HttpContext.Request.Body))
 | 
			
		||||
                {
 | 
			
		||||
                    var json = await reader.ReadToEndAsync();
 | 
			
		||||
 | 
			
		||||
                    var command = JsonConvert.DeserializeObject<ICommand>(json, _jsonSerialiserSettings);
 | 
			
		||||
 | 
			
		||||
                    _logger.LogDebug($"{_baseSchemeUrlAndPort}/command called, my state is {_node.State.GetType().FullName}");
 | 
			
		||||
 | 
			
		||||
                    var commandResponse = await _node.Accept(command);
 | 
			
		||||
 | 
			
		||||
                    json = JsonConvert.SerializeObject(commandResponse, _jsonSerialiserSettings);
 | 
			
		||||
 | 
			
		||||
                    return StatusCode(200, json);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception e)
 | 
			
		||||
            {
 | 
			
		||||
                _logger.LogError($"THERE WAS A PROBLEM ON NODE {_node.State.CurrentState.Id}", e);
 | 
			
		||||
                throw;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								src/Ocelot.Provider.Rafty/RaftyFileConfigurationSetter.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/Ocelot.Provider.Rafty/RaftyFileConfigurationSetter.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
namespace Ocelot.Provider.Rafty
 | 
			
		||||
{
 | 
			
		||||
    using System.Threading.Tasks;
 | 
			
		||||
    using Configuration.File;
 | 
			
		||||
    using Configuration.Setter;
 | 
			
		||||
    using global::Rafty.Concensus.Node;
 | 
			
		||||
    using global::Rafty.Infrastructure;
 | 
			
		||||
 | 
			
		||||
    public class RaftyFileConfigurationSetter : IFileConfigurationSetter
 | 
			
		||||
    {
 | 
			
		||||
        private readonly INode _node;
 | 
			
		||||
 | 
			
		||||
        public RaftyFileConfigurationSetter(INode node)
 | 
			
		||||
        {
 | 
			
		||||
            _node = node;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<Responses.Response> Set(FileConfiguration fileConfiguration)
 | 
			
		||||
        {
 | 
			
		||||
            var result = await _node.Accept(new UpdateFileConfiguration(fileConfiguration));
 | 
			
		||||
 | 
			
		||||
            if (result.GetType() == typeof(ErrorResponse<UpdateFileConfiguration>))
 | 
			
		||||
            {
 | 
			
		||||
                return new Responses.ErrorResponse(new UnableToSaveAcceptCommand($"unable to save file configuration to state machine"));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return new Responses.OkResponse();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,49 @@
 | 
			
		||||
namespace Ocelot.Provider.Rafty
 | 
			
		||||
{
 | 
			
		||||
    using System.Threading.Tasks;
 | 
			
		||||
    using global::Rafty.Concensus.Node;
 | 
			
		||||
    using global::Rafty.Infrastructure;
 | 
			
		||||
    using Microsoft.AspNetCore.Builder;
 | 
			
		||||
    using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
    using Middleware;
 | 
			
		||||
    using Microsoft.AspNetCore.Hosting;
 | 
			
		||||
 | 
			
		||||
    public static class RaftyMiddlewareConfigurationProvider
 | 
			
		||||
    {
 | 
			
		||||
        public static OcelotMiddlewareConfigurationDelegate Get = builder =>
 | 
			
		||||
        {
 | 
			
		||||
            if (UsingRafty(builder))
 | 
			
		||||
            {
 | 
			
		||||
                SetUpRafty(builder);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        private static bool UsingRafty(IApplicationBuilder builder)
 | 
			
		||||
        {
 | 
			
		||||
            var node = builder.ApplicationServices.GetService<INode>();
 | 
			
		||||
            if (node != null)
 | 
			
		||||
            {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static void SetUpRafty(IApplicationBuilder builder)
 | 
			
		||||
        {
 | 
			
		||||
            var applicationLifetime = builder.ApplicationServices.GetService<IApplicationLifetime>();
 | 
			
		||||
            applicationLifetime.ApplicationStopping.Register(() => OnShutdown(builder));
 | 
			
		||||
            var node = builder.ApplicationServices.GetService<INode>();
 | 
			
		||||
            var nodeId = builder.ApplicationServices.GetService<NodeId>();
 | 
			
		||||
            node.Start(nodeId);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static void OnShutdown(IApplicationBuilder app)
 | 
			
		||||
        {
 | 
			
		||||
            var node = app.ApplicationServices.GetService<INode>();
 | 
			
		||||
            node.Stop();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										334
									
								
								src/Ocelot.Provider.Rafty/SqlLiteLog.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										334
									
								
								src/Ocelot.Provider.Rafty/SqlLiteLog.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,334 @@
 | 
			
		||||
namespace Ocelot.Provider.Rafty
 | 
			
		||||
{
 | 
			
		||||
    using System;
 | 
			
		||||
    using System.Collections.Generic;
 | 
			
		||||
    using System.IO;
 | 
			
		||||
    using System.Threading;
 | 
			
		||||
    using System.Threading.Tasks;
 | 
			
		||||
    using global::Rafty.Infrastructure;
 | 
			
		||||
    using global::Rafty.Log;
 | 
			
		||||
    using Microsoft.Data.Sqlite;
 | 
			
		||||
    using Microsoft.Extensions.Logging;
 | 
			
		||||
    using Newtonsoft.Json;
 | 
			
		||||
 | 
			
		||||
    public class SqlLiteLog : ILog
 | 
			
		||||
    {
 | 
			
		||||
        private readonly string _path;
 | 
			
		||||
        private readonly SemaphoreSlim _sempaphore = new SemaphoreSlim(1, 1);
 | 
			
		||||
        private readonly ILogger _logger;
 | 
			
		||||
        private readonly NodeId _nodeId;
 | 
			
		||||
 | 
			
		||||
        public SqlLiteLog(NodeId nodeId, ILoggerFactory loggerFactory)
 | 
			
		||||
        {
 | 
			
		||||
            _logger = loggerFactory.CreateLogger<SqlLiteLog>();
 | 
			
		||||
            _nodeId = nodeId;
 | 
			
		||||
            _path = $"{nodeId.Id.Replace("/", "").Replace(":", "")}.db";
 | 
			
		||||
            _sempaphore.Wait();
 | 
			
		||||
 | 
			
		||||
            if (!File.Exists(_path))
 | 
			
		||||
            {
 | 
			
		||||
                var fs = File.Create(_path);
 | 
			
		||||
 | 
			
		||||
                fs.Dispose();
 | 
			
		||||
 | 
			
		||||
                using (var connection = new SqliteConnection($"Data Source={_path};"))
 | 
			
		||||
                {
 | 
			
		||||
                    connection.Open();
 | 
			
		||||
 | 
			
		||||
                    const string sql = @"create table logs (
 | 
			
		||||
                        id integer primary key,
 | 
			
		||||
                        data text not null
 | 
			
		||||
                    )";
 | 
			
		||||
 | 
			
		||||
                    using (var command = new SqliteCommand(sql, connection))
 | 
			
		||||
                    {
 | 
			
		||||
                        var result = command.ExecuteNonQuery();
 | 
			
		||||
 | 
			
		||||
                        _logger.LogInformation(result == 0
 | 
			
		||||
                            ? $"id: {_nodeId.Id} create database, result: {result}"
 | 
			
		||||
                            : $"id: {_nodeId.Id} did not create database., result: {result}");
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _sempaphore.Release();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<int> LastLogIndex()
 | 
			
		||||
        {
 | 
			
		||||
            _sempaphore.Wait();
 | 
			
		||||
            var result = 1;
 | 
			
		||||
            using (var connection = new SqliteConnection($"Data Source={_path};"))
 | 
			
		||||
            {
 | 
			
		||||
                connection.Open();
 | 
			
		||||
                var sql = @"select id from logs order by id desc limit 1";
 | 
			
		||||
                using (var command = new SqliteCommand(sql, connection))
 | 
			
		||||
                {
 | 
			
		||||
                    var index = Convert.ToInt32(await command.ExecuteScalarAsync());
 | 
			
		||||
                    if (index > result)
 | 
			
		||||
                    {
 | 
			
		||||
                        result = index;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _sempaphore.Release();
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<long> LastLogTerm()
 | 
			
		||||
        {
 | 
			
		||||
            _sempaphore.Wait();
 | 
			
		||||
            long result = 0;
 | 
			
		||||
            using (var connection = new SqliteConnection($"Data Source={_path};"))
 | 
			
		||||
            {
 | 
			
		||||
                connection.Open();
 | 
			
		||||
                var sql = @"select data from logs order by id desc limit 1";
 | 
			
		||||
                using (var command = new SqliteCommand(sql, connection))
 | 
			
		||||
                {
 | 
			
		||||
                    var data = Convert.ToString(await command.ExecuteScalarAsync());
 | 
			
		||||
                    var jsonSerializerSettings = new JsonSerializerSettings()
 | 
			
		||||
                    {
 | 
			
		||||
                        TypeNameHandling = TypeNameHandling.All
 | 
			
		||||
                    };
 | 
			
		||||
                    var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
 | 
			
		||||
                    if (log != null && log.Term > result)
 | 
			
		||||
                    {
 | 
			
		||||
                        result = log.Term;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _sempaphore.Release();
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<int> Count()
 | 
			
		||||
        {
 | 
			
		||||
            _sempaphore.Wait();
 | 
			
		||||
            var result = 0;
 | 
			
		||||
            using (var connection = new SqliteConnection($"Data Source={_path};"))
 | 
			
		||||
            {
 | 
			
		||||
                connection.Open();
 | 
			
		||||
                var sql = @"select count(id) from logs";
 | 
			
		||||
                using (var command = new SqliteCommand(sql, connection))
 | 
			
		||||
                {
 | 
			
		||||
                    var index = Convert.ToInt32(await command.ExecuteScalarAsync());
 | 
			
		||||
                    if (index > result)
 | 
			
		||||
                    {
 | 
			
		||||
                        result = index;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _sempaphore.Release();
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<int> Apply(LogEntry log)
 | 
			
		||||
        {
 | 
			
		||||
            _sempaphore.Wait();
 | 
			
		||||
            using (var connection = new SqliteConnection($"Data Source={_path};"))
 | 
			
		||||
            {
 | 
			
		||||
                connection.Open();
 | 
			
		||||
                var jsonSerializerSettings = new JsonSerializerSettings()
 | 
			
		||||
                {
 | 
			
		||||
                    TypeNameHandling = TypeNameHandling.All
 | 
			
		||||
                };
 | 
			
		||||
                var data = JsonConvert.SerializeObject(log, jsonSerializerSettings);
 | 
			
		||||
 | 
			
		||||
                //todo - sql injection dont copy this..
 | 
			
		||||
                var sql = $"insert into logs (data) values ('{data}')";
 | 
			
		||||
                _logger.LogInformation($"id: {_nodeId.Id}, sql: {sql}");
 | 
			
		||||
                using (var command = new SqliteCommand(sql, connection))
 | 
			
		||||
                {
 | 
			
		||||
                    var result = await command.ExecuteNonQueryAsync();
 | 
			
		||||
                    _logger.LogInformation($"id: {_nodeId.Id}, insert log result: {result}");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                sql = "select last_insert_rowid()";
 | 
			
		||||
                using (var command = new SqliteCommand(sql, connection))
 | 
			
		||||
                {
 | 
			
		||||
                    var result = await command.ExecuteScalarAsync();
 | 
			
		||||
                    _logger.LogInformation($"id: {_nodeId.Id}, about to release semaphore");
 | 
			
		||||
                    _sempaphore.Release();
 | 
			
		||||
                    _logger.LogInformation($"id: {_nodeId.Id}, saved log to sqlite");
 | 
			
		||||
                    return Convert.ToInt32(result);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task DeleteConflictsFromThisLog(int index, LogEntry logEntry)
 | 
			
		||||
        {
 | 
			
		||||
            _sempaphore.Wait();
 | 
			
		||||
            using (var connection = new SqliteConnection($"Data Source={_path};"))
 | 
			
		||||
            {
 | 
			
		||||
                connection.Open();
 | 
			
		||||
 | 
			
		||||
                //todo - sql injection dont copy this..
 | 
			
		||||
                var sql = $"select data from logs where id = {index};";
 | 
			
		||||
                _logger.LogInformation($"id: {_nodeId.Id} sql: {sql}");
 | 
			
		||||
                using (var command = new SqliteCommand(sql, connection))
 | 
			
		||||
                {
 | 
			
		||||
                    var data = Convert.ToString(await command.ExecuteScalarAsync());
 | 
			
		||||
                    var jsonSerializerSettings = new JsonSerializerSettings()
 | 
			
		||||
                    {
 | 
			
		||||
                        TypeNameHandling = TypeNameHandling.All
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    _logger.LogInformation($"id {_nodeId.Id} got log for index: {index}, data is {data} and new log term is {logEntry.Term}");
 | 
			
		||||
 | 
			
		||||
                    var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
 | 
			
		||||
                    if (logEntry != null && log != null && logEntry.Term != log.Term)
 | 
			
		||||
                    {
 | 
			
		||||
                        //todo - sql injection dont copy this..
 | 
			
		||||
                        var deleteSql = $"delete from logs where id >= {index};";
 | 
			
		||||
                        _logger.LogInformation($"id: {_nodeId.Id} sql: {deleteSql}");
 | 
			
		||||
                        using (var deleteCommand = new SqliteCommand(deleteSql, connection))
 | 
			
		||||
                        {
 | 
			
		||||
                            var result = await deleteCommand.ExecuteNonQueryAsync();
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _sempaphore.Release();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<bool> IsDuplicate(int index, LogEntry logEntry)
 | 
			
		||||
        {
 | 
			
		||||
            _sempaphore.Wait();
 | 
			
		||||
            using (var connection = new SqliteConnection($"Data Source={_path};"))
 | 
			
		||||
            {
 | 
			
		||||
                connection.Open();
 | 
			
		||||
 | 
			
		||||
                //todo - sql injection dont copy this..
 | 
			
		||||
                var sql = $"select data from logs where id = {index};";
 | 
			
		||||
                using (var command = new SqliteCommand(sql, connection))
 | 
			
		||||
                {
 | 
			
		||||
                    var data = Convert.ToString(await command.ExecuteScalarAsync());
 | 
			
		||||
                    var jsonSerializerSettings = new JsonSerializerSettings()
 | 
			
		||||
                    {
 | 
			
		||||
                        TypeNameHandling = TypeNameHandling.All
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
 | 
			
		||||
 | 
			
		||||
                    if (logEntry != null && log != null && logEntry.Term == log.Term)
 | 
			
		||||
                    {
 | 
			
		||||
                        _sempaphore.Release();
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _sempaphore.Release();
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<LogEntry> Get(int index)
 | 
			
		||||
        {
 | 
			
		||||
            _sempaphore.Wait();
 | 
			
		||||
            using (var connection = new SqliteConnection($"Data Source={_path};"))
 | 
			
		||||
            {
 | 
			
		||||
                connection.Open();
 | 
			
		||||
 | 
			
		||||
                //todo - sql injection dont copy this..
 | 
			
		||||
                var sql = $"select data from logs where id = {index}";
 | 
			
		||||
                using (var command = new SqliteCommand(sql, connection))
 | 
			
		||||
                {
 | 
			
		||||
                    var data = Convert.ToString(await command.ExecuteScalarAsync());
 | 
			
		||||
                    var jsonSerializerSettings = new JsonSerializerSettings()
 | 
			
		||||
                    {
 | 
			
		||||
                        TypeNameHandling = TypeNameHandling.All
 | 
			
		||||
                    };
 | 
			
		||||
                    var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
 | 
			
		||||
                    _sempaphore.Release();
 | 
			
		||||
                    return log;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<List<(int index, LogEntry logEntry)>> GetFrom(int index)
 | 
			
		||||
        {
 | 
			
		||||
            _sempaphore.Wait();
 | 
			
		||||
            var logsToReturn = new List<(int, LogEntry)>();
 | 
			
		||||
 | 
			
		||||
            using (var connection = new SqliteConnection($"Data Source={_path};"))
 | 
			
		||||
            {
 | 
			
		||||
                connection.Open();
 | 
			
		||||
 | 
			
		||||
                //todo - sql injection dont copy this..
 | 
			
		||||
                var sql = $"select id, data from logs where id >= {index}";
 | 
			
		||||
                using (var command = new SqliteCommand(sql, connection))
 | 
			
		||||
                {
 | 
			
		||||
                    using (var reader = await command.ExecuteReaderAsync())
 | 
			
		||||
                    {
 | 
			
		||||
                        while (reader.Read())
 | 
			
		||||
                        {
 | 
			
		||||
                            var id = Convert.ToInt32(reader[0]);
 | 
			
		||||
                            var data = (string)reader[1];
 | 
			
		||||
                            var jsonSerializerSettings = new JsonSerializerSettings()
 | 
			
		||||
                            {
 | 
			
		||||
                                TypeNameHandling = TypeNameHandling.All
 | 
			
		||||
                            };
 | 
			
		||||
                            var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
 | 
			
		||||
                            logsToReturn.Add((id, log));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                _sempaphore.Release();
 | 
			
		||||
                return logsToReturn;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<long> GetTermAtIndex(int index)
 | 
			
		||||
        {
 | 
			
		||||
            _sempaphore.Wait();
 | 
			
		||||
            long result = 0;
 | 
			
		||||
            using (var connection = new SqliteConnection($"Data Source={_path};"))
 | 
			
		||||
            {
 | 
			
		||||
                connection.Open();
 | 
			
		||||
 | 
			
		||||
                //todo - sql injection dont copy this..
 | 
			
		||||
                var sql = $"select data from logs where id = {index}";
 | 
			
		||||
                using (var command = new SqliteCommand(sql, connection))
 | 
			
		||||
                {
 | 
			
		||||
                    var data = Convert.ToString(await command.ExecuteScalarAsync());
 | 
			
		||||
                    var jsonSerializerSettings = new JsonSerializerSettings()
 | 
			
		||||
                    {
 | 
			
		||||
                        TypeNameHandling = TypeNameHandling.All
 | 
			
		||||
                    };
 | 
			
		||||
                    var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
 | 
			
		||||
                    if (log != null && log.Term > result)
 | 
			
		||||
                    {
 | 
			
		||||
                        result = log.Term;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _sempaphore.Release();
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task Remove(int indexOfCommand)
 | 
			
		||||
        {
 | 
			
		||||
            _sempaphore.Wait();
 | 
			
		||||
            using (var connection = new SqliteConnection($"Data Source={_path};"))
 | 
			
		||||
            {
 | 
			
		||||
                connection.Open();
 | 
			
		||||
 | 
			
		||||
                //todo - sql injection dont copy this..
 | 
			
		||||
                var deleteSql = $"delete from logs where id >= {indexOfCommand};";
 | 
			
		||||
                _logger.LogInformation($"id: {_nodeId.Id} Remove {deleteSql}");
 | 
			
		||||
                using (var deleteCommand = new SqliteCommand(deleteSql, connection))
 | 
			
		||||
                {
 | 
			
		||||
                    var result = await deleteCommand.ExecuteNonQueryAsync();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _sempaphore.Release();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								src/Ocelot.Provider.Rafty/UnableToSaveAcceptCommand.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/Ocelot.Provider.Rafty/UnableToSaveAcceptCommand.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
namespace Ocelot.Provider.Rafty
 | 
			
		||||
{
 | 
			
		||||
    using Errors;
 | 
			
		||||
    public class UnableToSaveAcceptCommand : Error
 | 
			
		||||
    {
 | 
			
		||||
        public UnableToSaveAcceptCommand(string message) 
 | 
			
		||||
            : base(message, OcelotErrorCode.UnknownError)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								src/Ocelot.Provider.Rafty/UpdateFileConfiguration.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/Ocelot.Provider.Rafty/UpdateFileConfiguration.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
namespace Ocelot.Provider.Rafty
 | 
			
		||||
{
 | 
			
		||||
    using Configuration.File;
 | 
			
		||||
    using global::Rafty.FiniteStateMachine;
 | 
			
		||||
 | 
			
		||||
    public class UpdateFileConfiguration : ICommand
 | 
			
		||||
    {
 | 
			
		||||
        public UpdateFileConfiguration(FileConfiguration configuration)
 | 
			
		||||
        {
 | 
			
		||||
            Configuration = configuration;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public FileConfiguration Configuration { get; private set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										103
									
								
								src/Ocelot.Tracing.Butterfly/ButterflyTracer.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								src/Ocelot.Tracing.Butterfly/ButterflyTracer.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
			
		||||
namespace Ocelot.Tracing.Butterfly
 | 
			
		||||
{
 | 
			
		||||
    using System;
 | 
			
		||||
    using System.Collections.Generic;
 | 
			
		||||
    using System.Linq;
 | 
			
		||||
    using System.Net.Http;
 | 
			
		||||
    using System.Threading;
 | 
			
		||||
    using System.Threading.Tasks;
 | 
			
		||||
    using global::Butterfly.Client.AspNetCore;
 | 
			
		||||
    using global::Butterfly.Client.Tracing;
 | 
			
		||||
    using global::Butterfly.OpenTracing;
 | 
			
		||||
    using Infrastructure.Extensions;
 | 
			
		||||
    using Microsoft.AspNetCore.Http;
 | 
			
		||||
    using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
 | 
			
		||||
    public class ButterflyTracer : DelegatingHandler, Logging.ITracer
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IServiceTracer _tracer;
 | 
			
		||||
        private const string PrefixSpanId = "ot-spanId";
 | 
			
		||||
 | 
			
		||||
        public ButterflyTracer(IServiceProvider services)
 | 
			
		||||
        {
 | 
			
		||||
            _tracer = services.GetService<IServiceTracer>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Event(HttpContext httpContext, string @event)
 | 
			
		||||
        {
 | 
			
		||||
            // todo - if the user isnt using tracing the code gets here and will blow up on 
 | 
			
		||||
            // _tracer.Tracer.TryExtract..
 | 
			
		||||
            if (_tracer == null)
 | 
			
		||||
            {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var span = httpContext.GetSpan();
 | 
			
		||||
 | 
			
		||||
            if (span == null)
 | 
			
		||||
            {
 | 
			
		||||
                var spanBuilder = new SpanBuilder($"server {httpContext.Request.Method} {httpContext.Request.Path}");
 | 
			
		||||
                if (_tracer.Tracer.TryExtract(out var spanContext, httpContext.Request.Headers, (c, k) => c[k].GetValue(),
 | 
			
		||||
                    c => c.Select(x => new KeyValuePair<string, string>(x.Key, x.Value.GetValue())).GetEnumerator()))
 | 
			
		||||
                {
 | 
			
		||||
                    spanBuilder.AsChildOf(spanContext);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                span = _tracer.Start(spanBuilder);
 | 
			
		||||
                httpContext.SetSpan(span);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            span?.Log(LogField.CreateNew().Event(@event));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Task<HttpResponseMessage> SendAsync(
 | 
			
		||||
            HttpRequestMessage request,
 | 
			
		||||
            CancellationToken cancellationToken, 
 | 
			
		||||
            Action<string> addTraceIdToRepo, 
 | 
			
		||||
            Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> baseSendAsync)
 | 
			
		||||
        {
 | 
			
		||||
            return _tracer.ChildTraceAsync($"httpclient {request.Method}", DateTimeOffset.UtcNow, span => TracingSendAsync(span, request, cancellationToken, addTraceIdToRepo, baseSendAsync));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected virtual async Task<HttpResponseMessage> TracingSendAsync(
 | 
			
		||||
            ISpan span,
 | 
			
		||||
            HttpRequestMessage request,
 | 
			
		||||
            CancellationToken cancellationToken, 
 | 
			
		||||
            Action<string> addTraceIdToRepo, 
 | 
			
		||||
            Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> baseSendAsync)
 | 
			
		||||
        {
 | 
			
		||||
            if (request.Headers.Contains(PrefixSpanId))
 | 
			
		||||
            {
 | 
			
		||||
                request.Headers.Remove(PrefixSpanId);
 | 
			
		||||
                request.Headers.TryAddWithoutValidation(PrefixSpanId, span.SpanContext.SpanId);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            addTraceIdToRepo(span.SpanContext.TraceId);
 | 
			
		||||
 | 
			
		||||
            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 baseSendAsync(request, cancellationToken);
 | 
			
		||||
 | 
			
		||||
            span.Log(LogField.CreateNew().ClientReceive());
 | 
			
		||||
 | 
			
		||||
            return responseMessage;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								src/Ocelot.Tracing.Butterfly/Ocelot.Tracing.Butterfly.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/Ocelot.Tracing.Butterfly/Ocelot.Tracing.Butterfly.csproj
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
<Project Sdk="Microsoft.NET.Sdk">
 | 
			
		||||
 | 
			
		||||
  <PropertyGroup>
 | 
			
		||||
    <TargetFramework>netstandard2.0</TargetFramework>
 | 
			
		||||
    <RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
 | 
			
		||||
    <NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
 | 
			
		||||
    <NoPackageAnalysis>true</NoPackageAnalysis>
 | 
			
		||||
    <Description>This package provides methods to integrate Butterfly tracing with Ocelot.</Description>
 | 
			
		||||
    <AssemblyTitle>Ocelot.Tracing.Butterfly</AssemblyTitle>
 | 
			
		||||
    <VersionPrefix>0.0.0-dev</VersionPrefix>
 | 
			
		||||
    <AssemblyName>Ocelot.Tracing.Butterfly</AssemblyName>
 | 
			
		||||
    <PackageId>Ocelot.Tracing.Butterfly</PackageId>
 | 
			
		||||
    <PackageTags>API Gateway;.NET core; Butterfly; ButterflyAPM</PackageTags>
 | 
			
		||||
    <PackageProjectUrl>https://github.com/ThreeMammals/Ocelot</PackageProjectUrl>
 | 
			
		||||
    <PackageProjectUrl>https://github.com/ThreeMammals/Ocelot</PackageProjectUrl>
 | 
			
		||||
    <RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
 | 
			
		||||
    <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
 | 
			
		||||
    <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
 | 
			
		||||
    <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
 | 
			
		||||
    <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
 | 
			
		||||
    <Authors>Tom Pallister</Authors>
 | 
			
		||||
    <CodeAnalysisRuleSet>..\..\codeanalysis.ruleset</CodeAnalysisRuleSet>
 | 
			
		||||
    <RootNamespace>Ocelot.Tracing.Butterfly</RootNamespace>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
 <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
 | 
			
		||||
    <DebugType>full</DebugType>
 | 
			
		||||
    <DebugSymbols>True</DebugSymbols>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <ProjectReference Include="..\Ocelot\Ocelot.csproj" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <PackageReference Include="Butterfly.Client" Version="0.0.8" />
 | 
			
		||||
    <PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
</Project>
 | 
			
		||||
							
								
								
									
										18
									
								
								src/Ocelot.Tracing.Butterfly/OcelotBuilderExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/Ocelot.Tracing.Butterfly/OcelotBuilderExtensions.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
namespace Ocelot.Tracing.Butterfly
 | 
			
		||||
{
 | 
			
		||||
    using System;
 | 
			
		||||
    using DependencyInjection;
 | 
			
		||||
    using global::Butterfly.Client.AspNetCore;
 | 
			
		||||
    using Logging;
 | 
			
		||||
    using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
 | 
			
		||||
    public static class OcelotBuilderExtensions
 | 
			
		||||
    {
 | 
			
		||||
        public static IOcelotBuilder AddButterfly(this IOcelotBuilder builder, Action<ButterflyOptions> settings)
 | 
			
		||||
        {
 | 
			
		||||
            builder.Services.AddSingleton<ITracer, ButterflyTracer>();
 | 
			
		||||
            builder.Services.AddButterfly(settings);
 | 
			
		||||
            return builder;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -10,8 +10,8 @@
 | 
			
		||||
    <AssemblyName>Ocelot</AssemblyName>
 | 
			
		||||
    <PackageId>Ocelot</PackageId>
 | 
			
		||||
    <PackageTags>API Gateway;.NET core</PackageTags>
 | 
			
		||||
    <PackageProjectUrl>https://github.com/TomPallister/Ocelot</PackageProjectUrl>
 | 
			
		||||
    <PackageProjectUrl>https://github.com/TomPallister/Ocelot</PackageProjectUrl>
 | 
			
		||||
    <PackageProjectUrl>https://github.com/ThreeMammals/Ocelot</PackageProjectUrl>
 | 
			
		||||
    <PackageProjectUrl>https://github.com/ThreeMammals/Ocelot</PackageProjectUrl>
 | 
			
		||||
    <PackageIconUrl>http://threemammals.com/images/ocelot_logo.png</PackageIconUrl>
 | 
			
		||||
    <RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
 | 
			
		||||
    <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user