From dbff2b9530213adf98dfa826dcad47ef8c9d5dd2 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Thu, 21 Jul 2016 20:28:22 +0100 Subject: [PATCH] messing around with the proxy mdh the proxy middleware --- .../HostUrlRepository/HostUrlMap.cs | 6 +-- ...Repository.cs => IHostUrlMapRepository.cs} | 2 +- ...ory.cs => InMemoryHostUrlMapRepository.cs} | 10 ++-- .../UrlPathMatcher/UrlPathMatch.cs | 5 +- .../UrlPathToUrlPathTemplateMatcher.cs | 6 ++- .../IUrlPathTemplateMapRepository.cs | 2 + .../InMemoryUrlPathTemplateMapRepository.cs | 13 +++++ .../Middleware/ProxyMiddleware.cs | 53 +++++++++++++++---- src/Ocelot/Startup.cs | 10 ++-- .../Fake/FakeService.cs | 34 ++++++++++++ .../Fake/FakeStartup.cs | 40 ++++++++++++++ test/Ocelot.AcceptanceTests/RouterTests.cs | 27 +++++++--- .../HostUrlMapRepositoryTests.cs | 2 +- .../UrlPathTemplateMapRepositoryTests.cs | 20 +++++++ .../UrlPathToUrlPathTemplateMatcherTests.cs | 18 ++++++- 15 files changed, 213 insertions(+), 35 deletions(-) rename src/Ocelot.Library/Infrastructure/HostUrlRepository/{IBaseUrlMapRepository.cs => IHostUrlMapRepository.cs} (76%) rename src/Ocelot.Library/Infrastructure/HostUrlRepository/{InMemoryBaseUrlMapRepository.cs => InMemoryHostUrlMapRepository.cs} (73%) create mode 100644 test/Ocelot.AcceptanceTests/Fake/FakeService.cs create mode 100644 test/Ocelot.AcceptanceTests/Fake/FakeStartup.cs diff --git a/src/Ocelot.Library/Infrastructure/HostUrlRepository/HostUrlMap.cs b/src/Ocelot.Library/Infrastructure/HostUrlRepository/HostUrlMap.cs index 1a13ee65..a9b6ce61 100644 --- a/src/Ocelot.Library/Infrastructure/HostUrlRepository/HostUrlMap.cs +++ b/src/Ocelot.Library/Infrastructure/HostUrlRepository/HostUrlMap.cs @@ -2,13 +2,13 @@ namespace Ocelot.Library.Infrastructure.HostUrlRepository { public class HostUrlMap { - public HostUrlMap(string downstreamHostUrl, string upstreamHostUrl) + public HostUrlMap(string urlPathTemplate, string upstreamHostUrl) { - DownstreamHostUrl = downstreamHostUrl; + UrlPathTemplate = urlPathTemplate; UpstreamHostUrl = upstreamHostUrl; } - public string DownstreamHostUrl {get;private set;} + public string UrlPathTemplate {get;private set;} public string UpstreamHostUrl {get;private set;} } } \ No newline at end of file diff --git a/src/Ocelot.Library/Infrastructure/HostUrlRepository/IBaseUrlMapRepository.cs b/src/Ocelot.Library/Infrastructure/HostUrlRepository/IHostUrlMapRepository.cs similarity index 76% rename from src/Ocelot.Library/Infrastructure/HostUrlRepository/IBaseUrlMapRepository.cs rename to src/Ocelot.Library/Infrastructure/HostUrlRepository/IHostUrlMapRepository.cs index 50a7e248..da78044c 100644 --- a/src/Ocelot.Library/Infrastructure/HostUrlRepository/IBaseUrlMapRepository.cs +++ b/src/Ocelot.Library/Infrastructure/HostUrlRepository/IHostUrlMapRepository.cs @@ -5,6 +5,6 @@ namespace Ocelot.Library.Infrastructure.HostUrlRepository public interface IHostUrlMapRepository { Response AddBaseUrlMap(HostUrlMap baseUrlMap); - Response GetBaseUrlMap(string downstreamUrl); + Response GetBaseUrlMap(string urlPathTemplate); } } \ No newline at end of file diff --git a/src/Ocelot.Library/Infrastructure/HostUrlRepository/InMemoryBaseUrlMapRepository.cs b/src/Ocelot.Library/Infrastructure/HostUrlRepository/InMemoryHostUrlMapRepository.cs similarity index 73% rename from src/Ocelot.Library/Infrastructure/HostUrlRepository/InMemoryBaseUrlMapRepository.cs rename to src/Ocelot.Library/Infrastructure/HostUrlRepository/InMemoryHostUrlMapRepository.cs index acb07542..61b45ebf 100644 --- a/src/Ocelot.Library/Infrastructure/HostUrlRepository/InMemoryBaseUrlMapRepository.cs +++ b/src/Ocelot.Library/Infrastructure/HostUrlRepository/InMemoryHostUrlMapRepository.cs @@ -12,23 +12,23 @@ namespace Ocelot.Library.Infrastructure.HostUrlRepository } public Response AddBaseUrlMap(HostUrlMap baseUrlMap) { - if(_routes.ContainsKey(baseUrlMap.DownstreamHostUrl)) + if(_routes.ContainsKey(baseUrlMap.UrlPathTemplate)) { return new ErrorResponse(new List(){new HostUrlMapKeyAlreadyExists()}); } - _routes.Add(baseUrlMap.DownstreamHostUrl, baseUrlMap.UpstreamHostUrl); + _routes.Add(baseUrlMap.UrlPathTemplate, baseUrlMap.UpstreamHostUrl); return new OkResponse(); } - public Response GetBaseUrlMap(string downstreamUrl) + public Response GetBaseUrlMap(string urlPathTemplate) { string upstreamUrl = null; - if(_routes.TryGetValue(downstreamUrl, out upstreamUrl)) + if(_routes.TryGetValue(urlPathTemplate, out upstreamUrl)) { - return new OkResponse(new HostUrlMap(downstreamUrl, upstreamUrl)); + return new OkResponse(new HostUrlMap(urlPathTemplate, upstreamUrl)); } return new ErrorResponse(new List(){new HostUrlMapKeyDoesNotExist()}); diff --git a/src/Ocelot.Library/Infrastructure/UrlPathMatcher/UrlPathMatch.cs b/src/Ocelot.Library/Infrastructure/UrlPathMatcher/UrlPathMatch.cs index d783d683..e7f21430 100644 --- a/src/Ocelot.Library/Infrastructure/UrlPathMatcher/UrlPathMatch.cs +++ b/src/Ocelot.Library/Infrastructure/UrlPathMatcher/UrlPathMatch.cs @@ -4,12 +4,15 @@ namespace Ocelot.Library.Infrastructure.UrlPathMatcher { public class UrlPathMatch { - public UrlPathMatch(bool match, List templateVariableNameAndValues) + public UrlPathMatch(bool match, List templateVariableNameAndValues, string urlPathTemplate) { Match = match; TemplateVariableNameAndValues = templateVariableNameAndValues; + UrlPathTemplate = urlPathTemplate; } public bool Match {get;private set;} public List TemplateVariableNameAndValues {get;private set;} + + public string UrlPathTemplate {get;private set;} } } \ No newline at end of file diff --git a/src/Ocelot.Library/Infrastructure/UrlPathMatcher/UrlPathToUrlPathTemplateMatcher.cs b/src/Ocelot.Library/Infrastructure/UrlPathMatcher/UrlPathToUrlPathTemplateMatcher.cs index a062e2e5..8ca2ded1 100644 --- a/src/Ocelot.Library/Infrastructure/UrlPathMatcher/UrlPathToUrlPathTemplateMatcher.cs +++ b/src/Ocelot.Library/Infrastructure/UrlPathMatcher/UrlPathToUrlPathTemplateMatcher.cs @@ -7,6 +7,8 @@ namespace Ocelot.Library.Infrastructure.UrlPathMatcher { public UrlPathMatch Match(string urlPath, string urlPathTemplate) { + var urlPathTemplateCopy = urlPathTemplate; + var templateKeysAndValues = new List(); urlPath = urlPath.ToLower(); @@ -37,12 +39,12 @@ namespace Ocelot.Library.Infrastructure.UrlPathMatcher } else { - return new UrlPathMatch(false, templateKeysAndValues); + return new UrlPathMatch(false, templateKeysAndValues, string.Empty); } } counterForUrl++; } - return new UrlPathMatch(true, templateKeysAndValues); + return new UrlPathMatch(true, templateKeysAndValues, urlPathTemplateCopy); } private string GetPlaceholderVariableValue(string urlPath, int counterForUrl) diff --git a/src/Ocelot.Library/Infrastructure/UrlPathTemplateRepository/IUrlPathTemplateMapRepository.cs b/src/Ocelot.Library/Infrastructure/UrlPathTemplateRepository/IUrlPathTemplateMapRepository.cs index f847c7ba..b63ecfc6 100644 --- a/src/Ocelot.Library/Infrastructure/UrlPathTemplateRepository/IUrlPathTemplateMapRepository.cs +++ b/src/Ocelot.Library/Infrastructure/UrlPathTemplateRepository/IUrlPathTemplateMapRepository.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Ocelot.Library.Infrastructure.Responses; namespace Ocelot.Library.Infrastructure.UrlPathTemplateRepository @@ -6,5 +7,6 @@ namespace Ocelot.Library.Infrastructure.UrlPathTemplateRepository { Response AddUrlPathTemplateMap(UrlPathTemplateMap urlPathMap); Response GetUrlPathTemplateMap(string downstreamUrlPathTemplate); + Response> All { get; } } } \ No newline at end of file diff --git a/src/Ocelot.Library/Infrastructure/UrlPathTemplateRepository/InMemoryUrlPathTemplateMapRepository.cs b/src/Ocelot.Library/Infrastructure/UrlPathTemplateRepository/InMemoryUrlPathTemplateMapRepository.cs index 257f1d3f..b56c6b95 100644 --- a/src/Ocelot.Library/Infrastructure/UrlPathTemplateRepository/InMemoryUrlPathTemplateMapRepository.cs +++ b/src/Ocelot.Library/Infrastructure/UrlPathTemplateRepository/InMemoryUrlPathTemplateMapRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Ocelot.Library.Infrastructure.Responses; namespace Ocelot.Library.Infrastructure.UrlPathTemplateRepository @@ -11,6 +12,18 @@ namespace Ocelot.Library.Infrastructure.UrlPathTemplateRepository { _routes = new Dictionary(); } + + public Response> All + { + get + { + var routes = _routes + .Select(r => new UrlPathTemplateMap(r.Key, r.Value)) + .ToList(); + return new OkResponse>(routes); + } + } + public Response AddUrlPathTemplateMap(UrlPathTemplateMap urlPathMap) { if(_routes.ContainsKey(urlPathMap.DownstreamUrlPathTemplate)) diff --git a/src/Ocelot.Library/Middleware/ProxyMiddleware.cs b/src/Ocelot.Library/Middleware/ProxyMiddleware.cs index ddc94fcf..6c7ad7ef 100644 --- a/src/Ocelot.Library/Middleware/ProxyMiddleware.cs +++ b/src/Ocelot.Library/Middleware/ProxyMiddleware.cs @@ -1,26 +1,61 @@ +using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Ocelot.Library.Infrastructure.HostUrlRepository; +using Ocelot.Library.Infrastructure.UrlPathMatcher; +using Ocelot.Library.Infrastructure.UrlPathTemplateRepository; namespace Ocelot.Library.Middleware { public class ProxyMiddleware { private readonly RequestDelegate _next; - - public ProxyMiddleware(RequestDelegate next) + private readonly IUrlPathToUrlPathTemplateMatcher _urlMatcher; + private readonly IUrlPathTemplateMapRepository _urlPathRepository; + private readonly IHostUrlMapRepository _hostUrlRepository; + public ProxyMiddleware(RequestDelegate next, + IUrlPathToUrlPathTemplateMatcher urlMatcher, + IUrlPathTemplateMapRepository urlPathRepository, + IHostUrlMapRepository hostUrlRepository) { _next = next; + _urlMatcher = urlMatcher; + _urlPathRepository = urlPathRepository; + _hostUrlRepository = hostUrlRepository; } public async Task Invoke(HttpContext context) { - //get the downstream host from the request context - //get the upstream host from the host repository - //if no upstream host fail this request - //get the downstream path from the request context - //get the downstream path template from the path template finder - //todo think about variables.. - //add any query string.. + + var path = context.Request.Path.ToString(); + + var templates = _urlPathRepository.All; + + UrlPathMatch urlPathMatch = null; + string upstreamPathUrl = string.Empty; + + foreach (var template in templates.Data) + { + urlPathMatch = _urlMatcher.Match(path, template.DownstreamUrlPathTemplate); + + if (urlPathMatch.Match) + { + upstreamPathUrl = template.UpstreamUrlPathTemplate; + break; + } + } + + if (!urlPathMatch.Match) + { + throw new Exception("BOOOM TING! no match"); + } + + var upstreamHostUrl = _hostUrlRepository.GetBaseUrlMap(urlPathMatch.UrlPathTemplate); + + //now map the variables from the url path to the upstream url path + + + await _next.Invoke(context); } } diff --git a/src/Ocelot/Startup.cs b/src/Ocelot/Startup.cs index 83cc57a8..e13d551d 100644 --- a/src/Ocelot/Startup.cs +++ b/src/Ocelot/Startup.cs @@ -37,11 +37,11 @@ namespace Ocelot loggerFactory.AddDebug(); app.UseProxy(); - - app.Run(async context => - { - await context.Response.WriteAsync("Hello from Tom"); - }); + //app.Run() + // app.Run(async context => + // { + // await context.Response.WriteAsync("Hello from Tom"); + // }); } } } diff --git a/test/Ocelot.AcceptanceTests/Fake/FakeService.cs b/test/Ocelot.AcceptanceTests/Fake/FakeService.cs new file mode 100644 index 00000000..56f97ce5 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/Fake/FakeService.cs @@ -0,0 +1,34 @@ +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; + +namespace Ocelot.AcceptanceTests.Fake +{ + public class FakeService + { + private Task _handler; + private IWebHost _webHostBuilder; + + public void Start(string url) + { + _webHostBuilder = new WebHostBuilder() + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .UseStartup() + .Build(); + + _handler = Task.Run(() => _webHostBuilder.Run()); + } + + public void Stop() + { + if(_webHostBuilder != null) + { + _webHostBuilder.Dispose(); + _handler.Wait(); + } + } + } +} \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/Fake/FakeStartup.cs b/test/Ocelot.AcceptanceTests/Fake/FakeStartup.cs new file mode 100644 index 00000000..35d5e644 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/Fake/FakeStartup.cs @@ -0,0 +1,40 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Ocelot.AcceptanceTests.Fake +{ + public class FakeStartup + { + public FakeStartup(IHostingEnvironment env) + { + var builder = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) + .AddEnvironmentVariables(); + Configuration = builder.Build(); + } + + public IConfigurationRoot Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + // Add framework services. + + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + app.Run(async context => + { + await context.Response.WriteAsync("Hello from Laura"); + }); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/RouterTests.cs b/test/Ocelot.AcceptanceTests/RouterTests.cs index ecc468ae..0381ff41 100644 --- a/test/Ocelot.AcceptanceTests/RouterTests.cs +++ b/test/Ocelot.AcceptanceTests/RouterTests.cs @@ -4,38 +4,51 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; using Xunit; +using Ocelot.AcceptanceTests.Fake; +using Shouldly; namespace Ocelot.AcceptanceTests { public class RouterTests : IDisposable { + private FakeService _fakeService; private readonly TestServer _server; private readonly HttpClient _client; public RouterTests() { - // Arrange _server = new TestServer(new WebHostBuilder() .UseStartup()); _client = _server.CreateClient(); + _fakeService = new FakeService(); } [Fact] - public async Task ReturnHelloWorld() + public void hello_world() { + var response = _client.GetAsync("/").Result; + response.EnsureSuccessStatusCode(); + + var responseString = response.Content.ReadAsStringAsync().Result; + responseString.ShouldBe("Hello from Tom"); + } + + [Fact] + public async Task can_route_request() + { + _fakeService.Start("http://localhost:5001"); + // Act var response = await _client.GetAsync("/"); response.EnsureSuccessStatusCode(); var responseString = await response.Content.ReadAsStringAsync(); - - // Assert - Assert.Equal("Hello from Tom", - responseString); + responseString.ShouldBe("Hello from Laura"); } public void Dispose() - { + { + _fakeService.Stop(); _client.Dispose(); _server.Dispose(); } diff --git a/test/Ocelot.UnitTests/HostUrlMapRepositoryTests.cs b/test/Ocelot.UnitTests/HostUrlMapRepositoryTests.cs index b22a6fb1..2bcd998f 100644 --- a/test/Ocelot.UnitTests/HostUrlMapRepositoryTests.cs +++ b/test/Ocelot.UnitTests/HostUrlMapRepositoryTests.cs @@ -77,7 +77,7 @@ namespace Ocelot.UnitTests private void ThenTheRouteIsReturned() { - _getRouteResponse.Data.DownstreamHostUrl.ShouldBe(_downstreamBaseUrl); + _getRouteResponse.Data.UrlPathTemplate.ShouldBe(_downstreamBaseUrl); _getRouteResponse.Data.UpstreamHostUrl.ShouldBe(_upstreamBaseUrl); } diff --git a/test/Ocelot.UnitTests/UrlPathTemplateMapRepositoryTests.cs b/test/Ocelot.UnitTests/UrlPathTemplateMapRepositoryTests.cs index 71ddd2c3..6dd8f8ed 100644 --- a/test/Ocelot.UnitTests/UrlPathTemplateMapRepositoryTests.cs +++ b/test/Ocelot.UnitTests/UrlPathTemplateMapRepositoryTests.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Ocelot.Library.Infrastructure.Responses; using Ocelot.Library.Infrastructure.UrlPathTemplateRepository; using Shouldly; @@ -12,6 +13,7 @@ namespace Ocelot.UnitTests private IUrlPathTemplateMapRepository _repository; private Response _response; private Response _getResponse; + private Response> _listResponse; public UrlPathTemplateMapRepositoryTests() { @@ -34,6 +36,14 @@ namespace Ocelot.UnitTests WhenIRetrieveTheUrlPathByDownstreamUrl(); ThenTheUrlPathIsReturned(); } + + [Fact] + public void can_get_all_urls() + { + GivenIHaveSetUpADownstreamUrlPathAndAnUpstreamUrlPath("/api2", "http://www.someapi.com/api2"); + WhenIRetrieveTheUrls(); + ThenTheUrlsAreReturned(); + } [Fact] public void should_return_error_response_when_url_path_already_used() @@ -75,12 +85,22 @@ namespace Ocelot.UnitTests _getResponse = _repository.GetUrlPathTemplateMap(_downstreamUrlPath); } + private void WhenIRetrieveTheUrls() + { + _listResponse = _repository.All; + } + private void ThenTheUrlPathIsReturned() { _getResponse.Data.DownstreamUrlPathTemplate.ShouldBe(_downstreamUrlPath); _getResponse.Data.UpstreamUrlPathTemplate.ShouldBe(_upstreamUrlPath); } + private void ThenTheUrlsAreReturned() + { + _listResponse.Data.Count.ShouldBeGreaterThan(0); + } + private void GivenIHaveSetUpADownstreamUrlPathAndAnUpstreamUrlPath(string downstream, string upstreamApiUrl) { GivenIHaveAnUpstreamUrlPath(upstreamApiUrl); diff --git a/test/Ocelot.UnitTests/UrlPathToUrlPathTemplateMatcherTests.cs b/test/Ocelot.UnitTests/UrlPathToUrlPathTemplateMatcherTests.cs index cfc63319..1219476a 100644 --- a/test/Ocelot.UnitTests/UrlPathToUrlPathTemplateMatcherTests.cs +++ b/test/Ocelot.UnitTests/UrlPathToUrlPathTemplateMatcherTests.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Ocelot.Library.Infrastructure.UrlPathMatcher; @@ -25,7 +26,7 @@ namespace Ocelot.UnitTests WhenIMatchThePaths(); ThenTheResultIsTrue(); ThenTheTemplatesDictionaryIs(new List()); - + ThenTheUrlPathTemplateIs("api/product/products/"); } [Fact] @@ -41,6 +42,7 @@ namespace Ocelot.UnitTests WhenIMatchThePaths(); ThenTheResultIsTrue(); ThenTheTemplatesDictionaryIs(expectedTemplates); + ThenTheUrlPathTemplateIs("api/product/products/{productId}/variants/"); } [Fact] @@ -56,6 +58,8 @@ namespace Ocelot.UnitTests WhenIMatchThePaths(); ThenTheResultIsTrue(); ThenTheTemplatesDictionaryIs(expectedTemplates); + ThenTheUrlPathTemplateIs("api/product/products/{productId}"); + } [Fact] @@ -72,6 +76,8 @@ namespace Ocelot.UnitTests WhenIMatchThePaths(); ThenTheResultIsTrue(); ThenTheTemplatesDictionaryIs(expectedTemplates); + ThenTheUrlPathTemplateIs("api/product/products/{productId}/{categoryId}"); + } [Fact] @@ -88,6 +94,8 @@ namespace Ocelot.UnitTests WhenIMatchThePaths(); ThenTheResultIsTrue(); ThenTheTemplatesDictionaryIs(expectedTemplates); + ThenTheUrlPathTemplateIs("api/product/products/{productId}/categories/{categoryId}"); + } [Fact] @@ -105,6 +113,8 @@ namespace Ocelot.UnitTests WhenIMatchThePaths(); ThenTheResultIsTrue(); ThenTheTemplatesDictionaryIs(expectedTemplates); + ThenTheUrlPathTemplateIs("api/product/products/{productId}/categories/{categoryId}/variant/{variantId}"); + } [Fact] @@ -121,6 +131,8 @@ namespace Ocelot.UnitTests WhenIMatchThePaths(); ThenTheResultIsTrue(); ThenTheTemplatesDictionaryIs(expectedTemplates); + ThenTheUrlPathTemplateIs("api/product/products/{productId}/categories/{categoryId}/variant/"); + } private void ThenTheTemplatesDictionaryIs(List expectedResults) @@ -133,6 +145,10 @@ namespace Ocelot.UnitTests } } + private void ThenTheUrlPathTemplateIs(string expectedUrlPathTemplate) + { + _result.UrlPathTemplate.ShouldBe(expectedUrlPathTemplate); + } private void GivenIHaveADownstreamPath(string downstreamPath) { _downstreamPath = downstreamPath;