diff --git a/samples/OcelotEureka/ApiGateway/ApiGateway.csproj b/samples/OcelotEureka/ApiGateway/ApiGateway.csproj new file mode 100644 index 00000000..d2ec8f48 --- /dev/null +++ b/samples/OcelotEureka/ApiGateway/ApiGateway.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp2.0 + + + + + + + + + PreserveNewest + + + + + + + + + diff --git a/samples/OcelotEureka/ApiGateway/Program.cs b/samples/OcelotEureka/ApiGateway/Program.cs new file mode 100644 index 00000000..28d344c9 --- /dev/null +++ b/samples/OcelotEureka/ApiGateway/Program.cs @@ -0,0 +1,38 @@ +namespace ApiGateway +{ + using Microsoft.AspNetCore; + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.Configuration; + using Ocelot.DependencyInjection; + using Ocelot.Middleware; + + public class Program + { + public static void Main(string[] args) + { + BuildWebHost(args).Run(); + } + + public static IWebHost BuildWebHost(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseUrls("http://localhost:5000") + .ConfigureAppConfiguration((hostingContext, config) => + { + config + .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) + .AddJsonFile("appsettings.json", true, true) + .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) + .AddJsonFile("ocelot.json") + .AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddOcelot(); + }) + .Configure(a => + { + a.UseOcelot().Wait(); + }) + .Build(); + } +} diff --git a/samples/OcelotEureka/ApiGateway/Properties/launchSettings.json b/samples/OcelotEureka/ApiGateway/Properties/launchSettings.json new file mode 100644 index 00000000..c1b4df87 --- /dev/null +++ b/samples/OcelotEureka/ApiGateway/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:54060/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "ApiGateway": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:54061/" + } + } +} diff --git a/samples/OcelotEureka/ApiGateway/appsettings.json b/samples/OcelotEureka/ApiGateway/appsettings.json new file mode 100644 index 00000000..4a098bc4 --- /dev/null +++ b/samples/OcelotEureka/ApiGateway/appsettings.json @@ -0,0 +1,26 @@ +{ + "Logging": { + "IncludeScopes": true, + "LogLevel": { + "Default": "Trace", + "System": "Information", + "Microsoft": "Information" + } + }, + "spring": { + "application": { "name": "Ocelot-Gateway" }, + "cloud": { + "config": { + "uri": "http://localhost:5000", + "validateCertificates": false + } + } + }, + "eureka": { + "client": { + "serviceUrl": "http://localhost:8761/eureka/", + "shouldRegisterWithEureka": false, + "validateCertificates": false + } + } +} diff --git a/samples/OcelotEureka/ApiGateway/ocelot.json b/samples/OcelotEureka/ApiGateway/ocelot.json new file mode 100644 index 00000000..b8694ddd --- /dev/null +++ b/samples/OcelotEureka/ApiGateway/ocelot.json @@ -0,0 +1,23 @@ +{ + "ReRoutes": [ + { + "DownstreamPathTemplate": "/api/Category", + "DownstreamScheme": "http", + "UpstreamPathTemplate": "/Category", + "UseServiceDiscovery": true, + "ServiceName": "ncore-rat", + "UpstreamHttpMethod": [ "Get" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10000, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + } + ], + "GlobalConfiguration": { + "RequestIdKey": "OcRequestId", + "AdministrationPath": "/administration", + "ServiceDiscoveryProvider": { "Type": "Eureka" } + } +} diff --git a/samples/OcelotEureka/DownstreamService/Controllers/CategoryController.cs b/samples/OcelotEureka/DownstreamService/Controllers/CategoryController.cs new file mode 100644 index 00000000..53350be7 --- /dev/null +++ b/samples/OcelotEureka/DownstreamService/Controllers/CategoryController.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; + +namespace DownstreamService.Controllers +{ + [Route("api/[controller]")] + public class CategoryController : Controller + { + // GET api/values + [HttpGet] + public IEnumerable Get() + { + return new[] { "category1", "category2" }; + } + } +} diff --git a/samples/OcelotEureka/DownstreamService/DownstreamService.csproj b/samples/OcelotEureka/DownstreamService/DownstreamService.csproj new file mode 100644 index 00000000..c1c23abd --- /dev/null +++ b/samples/OcelotEureka/DownstreamService/DownstreamService.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp2.0 + + + + + + + + + + + + + + + + diff --git a/samples/OcelotEureka/DownstreamService/Program.cs b/samples/OcelotEureka/DownstreamService/Program.cs new file mode 100644 index 00000000..ddce0c28 --- /dev/null +++ b/samples/OcelotEureka/DownstreamService/Program.cs @@ -0,0 +1,20 @@ +using System; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; + +namespace DownstreamService +{ + public class Program + { + public static void Main(string[] args) + { + BuildWebHost(args).Run(); + } + + public static IWebHost BuildWebHost(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseUrls($"http://{Environment.MachineName}:5001") + .UseStartup() + .Build(); + } +} diff --git a/samples/OcelotEureka/DownstreamService/Properties/launchSettings.json b/samples/OcelotEureka/DownstreamService/Properties/launchSettings.json new file mode 100644 index 00000000..0c084db8 --- /dev/null +++ b/samples/OcelotEureka/DownstreamService/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:53908/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "api/values", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "DownstreamService": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "api/values", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:53909/" + } + } +} diff --git a/samples/OcelotEureka/DownstreamService/Startup.cs b/samples/OcelotEureka/DownstreamService/Startup.cs new file mode 100644 index 00000000..b3924be6 --- /dev/null +++ b/samples/OcelotEureka/DownstreamService/Startup.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace DownstreamService +{ + using Steeltoe.Discovery.Client; + + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddDiscoveryClient(Configuration); + services.AddMvc(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseDiscoveryClient(); + app.UseMvc(); + } + } +} diff --git a/samples/OcelotEureka/DownstreamService/appsettings.Development.json b/samples/OcelotEureka/DownstreamService/appsettings.Development.json new file mode 100644 index 00000000..fa8ce71a --- /dev/null +++ b/samples/OcelotEureka/DownstreamService/appsettings.Development.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/samples/OcelotEureka/DownstreamService/appsettings.json b/samples/OcelotEureka/DownstreamService/appsettings.json new file mode 100644 index 00000000..4bebaa50 --- /dev/null +++ b/samples/OcelotEureka/DownstreamService/appsettings.json @@ -0,0 +1,24 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { "Default": "Warning" } + }, + "spring": { + "application": { "name": "ncore-rat" }, + "cloud": { + "config": { + "uri": "http://localhost:5001", + "validate_certificates": false + } + } + }, + "eureka": { + "client": { + "serviceUrl": "http://localhost:8761/eureka/", + "shouldFetchRegistry": false, + "validateCertificates": false + }, + "instance": { "port": 5001 } + } +} + diff --git a/samples/OcelotEureka/OcelotEureka.sln b/samples/OcelotEureka/OcelotEureka.sln new file mode 100644 index 00000000..62eb6ebc --- /dev/null +++ b/samples/OcelotEureka/OcelotEureka.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27428.2027 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DownstreamService", "./DownstreamService/DownstreamService.csproj", "{2982C147-9446-47FE-862E-E689B64CC7E7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiGateway", "./ApiGateway/ApiGateway.csproj", "{006CF27E-5400-43E9-B511-C54EC1B9C546}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2982C147-9446-47FE-862E-E689B64CC7E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2982C147-9446-47FE-862E-E689B64CC7E7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2982C147-9446-47FE-862E-E689B64CC7E7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2982C147-9446-47FE-862E-E689B64CC7E7}.Release|Any CPU.Build.0 = Release|Any CPU + {006CF27E-5400-43E9-B511-C54EC1B9C546}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {006CF27E-5400-43E9-B511-C54EC1B9C546}.Debug|Any CPU.Build.0 = Debug|Any CPU + {006CF27E-5400-43E9-B511-C54EC1B9C546}.Release|Any CPU.ActiveCfg = Release|Any CPU + {006CF27E-5400-43E9-B511-C54EC1B9C546}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2C604707-2EA1-4CCF-A89C-22B613052C8D} + EndGlobalSection +EndGlobal diff --git a/samples/OcelotEureka/README.md b/samples/OcelotEureka/README.md new file mode 100644 index 00000000..aa803743 --- /dev/null +++ b/samples/OcelotEureka/README.md @@ -0,0 +1,39 @@ +#Example how to use Eureka service discovery + +I created this becasue users are having trouble getting Eureka to work with Ocelot, hopefully this helps. +Please review the implementation of the individual servics to understand how everything fits together. + +##Instructions + +1. Get Eureka installed and running... + + ``` + $ git clone https://github.com/spring-cloud-samples/eureka.git + $ cd eureka + $ mvnw spring-boot:run + ``` + Leave the service running + +2. Get Downstream service running and registered with Eureka + + ``` + cd ./DownstreamService/ + dotnet run + ``` + + Leave the service running + +3. Get API Gateway running and collecting services from Eureka + + ``` + cd ./ApiGateway/ + dotnet run + ``` + + Leave the service running + +4. Make a http request to http://localhost:5000/category you should get the following response + + ```json + ["category1","category2"] + ``` \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs index 203d1b9d..69743c06 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs @@ -18,6 +18,11 @@ namespace Ocelot.LoadBalancer.LoadBalancers public async Task> Lease() { //todo no point spinning a task up here, also first or default could be null.. + if (_services == null || _services.Count == 0) + { + return new ErrorResponse(new ServicesAreEmptyError("There were no services in NoLoadBalancer")); + } + var service = await Task.FromResult(_services.FirstOrDefault()); return new OkResponse(service.HostAndPort); } diff --git a/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs b/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs index 3e9e1a4a..55e7a435 100644 --- a/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs +++ b/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs @@ -76,16 +76,16 @@ namespace Ocelot.Benchmarks response.EnsureSuccessStatusCode(); } - // * Summary * - // BenchmarkDotNet=v0.10.13, OS=macOS 10.12.6 (16G1212) [Darwin 16.7.0] - // Intel Core i5-4278U CPU 2.60GHz (Haswell), 1 CPU, 4 logical cores and 2 physical cores - // .NET Core SDK=2.1.4 - // [Host] : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT - // DefaultJob : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT + // * Summary* + // BenchmarkDotNet = v0.10.13, OS = macOS 10.12.6 (16G1212) [Darwin 16.7.0] + // Intel Core i5-4278U CPU 2.60GHz(Haswell), 1 CPU, 4 logical cores and 2 physical cores + //.NET Core SDK = 2.1.4 - // Method | Mean | Error | StdDev | StdErr | Min | Q1 | Median | Q3 | Max | Op/s | Scaled | Gen 0 | Gen 1 | Allocated | - // --------- |---------:|----------:|----------:|----------:|---------:|---------:|---------:|---------:|---------:|------:|-------:|--------:|-------:|----------:| - // Baseline | 2.102 ms | 0.0292 ms | 0.0273 ms | 0.0070 ms | 2.063 ms | 2.080 ms | 2.093 ms | 2.122 ms | 2.152 ms | 475.8 | 1.00 | 31.2500 | 3.9063 | 1.63 KB | + // [Host] : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT + // DefaultJob : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT + // Method | Mean | Error | StdDev | StdErr | Min | Q1 | Median | Q3 | Max | Op/s | Scaled | Gen 0 | Gen 1 | Allocated | + // --------- |---------:|----------:|----------:|----------:|---------:|---------:|---------:|---------:|---------:|------:|-------:|--------:|-------:|----------:| + // Baseline | 2.102 ms | 0.0292 ms | 0.0273 ms | 0.0070 ms | 2.063 ms | 2.080 ms | 2.093 ms | 2.122 ms | 2.152 ms | 475.8 | 1.00 | 31.2500 | 3.9063 | 1.63 KB | private void GivenOcelotIsRunning(string url) { @@ -121,7 +121,7 @@ namespace Ocelot.Benchmarks public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) { - var configurationPath = Path.Combine(AppContext.BaseDirectory, "ocelot.json");; + var configurationPath = Path.Combine(AppContext.BaseDirectory, "ocelot.json"); var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); diff --git a/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs b/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs index 3f02f48b..678c1934 100644 --- a/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs +++ b/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs @@ -43,8 +43,6 @@ namespace Ocelot.Benchmarks // .NET Core SDK=2.1.4 // [Host] : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT // DefaultJob : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT - - // Method | Mean | Error | StdDev | StdErr | Min | Q1 | Median | Q3 | Max | Op/s | // ----------- |---------:|----------:|----------:|----------:|---------:|---------:|---------:|---------:|---------:|----------:| // Benchmark1 | 3.133 us | 0.0492 us | 0.0460 us | 0.0119 us | 3.082 us | 3.100 us | 3.122 us | 3.168 us | 3.233 us | 319,161.9 | diff --git a/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs b/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs index 92806cac..74b5c4b0 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs @@ -29,6 +29,33 @@ namespace Ocelot.UnitTests.LoadBalancer .BDDfy(); } + [Fact] + public void should_return_error_if_no_services() + { + var services = new List(); + + this.Given(x => x.GivenServices(services)) + .When(x => x.WhenIGetTheNextHostAndPort()) + .Then(x => x.ThenThereIsAnError()) + .BDDfy(); + } + + [Fact] + public void should_return_error_if_null_services() + { + List services = null; + + this.Given(x => x.GivenServices(services)) + .When(x => x.WhenIGetTheNextHostAndPort()) + .Then(x => x.ThenThereIsAnError()) + .BDDfy(); + } + + private void ThenThereIsAnError() + { + _result.IsError.ShouldBeTrue(); + } + private void GivenServices(List services) { _services = services;