first passing proxy acceptnace test yeyyy!

This commit is contained in:
TomPallister 2016-09-07 21:47:39 +01:00
parent 71b7e7743e
commit 03ef47038a
12 changed files with 264 additions and 43 deletions

View File

@ -0,0 +1,16 @@
using System.Collections.Generic;
using Ocelot.Library.Infrastructure.UrlMatcher;
namespace Ocelot.Library.Infrastructure.DownstreamRouteFinder
{
public class DownstreamRoute
{
public DownstreamRoute(List<TemplateVariableNameAndValue> templateVariableNameAndValues, string downstreamUrlTemplate)
{
TemplateVariableNameAndValues = templateVariableNameAndValues;
DownstreamUrlTemplate = downstreamUrlTemplate;
}
public List<TemplateVariableNameAndValue> TemplateVariableNameAndValues { get; private set; }
public string DownstreamUrlTemplate { get; private set; }
}
}

View File

@ -0,0 +1,38 @@
using System.Collections.Generic;
using Microsoft.Extensions.Options;
using Ocelot.Library.Infrastructure.Responses;
using Ocelot.Library.Infrastructure.UrlMatcher;
namespace Ocelot.Library.Infrastructure.DownstreamRouteFinder
{
public class DownstreamRouteFinder : IDownstreamRouteFinder
{
private readonly IOptions<Configuration.Configuration> _configuration;
private readonly IUrlPathToUrlTemplateMatcher _urlMatcher;
public DownstreamRouteFinder(IOptions<Configuration.Configuration> configuration, IUrlPathToUrlTemplateMatcher urlMatcher)
{
_configuration = configuration;
_urlMatcher = urlMatcher;
}
public Response<DownstreamRoute> FindDownstreamRoute(string upstreamUrlPath)
{
foreach (var template in _configuration.Value.ReRoutes)
{
var urlMatch = _urlMatcher.Match(upstreamUrlPath, template.UpstreamTemplate);
if (urlMatch.Match)
{
return new OkResponse<DownstreamRoute>(new DownstreamRoute(urlMatch.TemplateVariableNameAndValues, template.DownstreamTemplate));
}
}
return new ErrorResponse<DownstreamRoute>(new List<Error>
{
new UnableToFindDownstreamRouteError()
});
}
}
}

View File

@ -0,0 +1,9 @@
using Ocelot.Library.Infrastructure.Responses;
namespace Ocelot.Library.Infrastructure.DownstreamRouteFinder
{
public interface IDownstreamRouteFinder
{
Response<DownstreamRoute> FindDownstreamRoute(string upstreamUrlPath);
}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Ocelot.Library.Infrastructure.Responses;
namespace Ocelot.Library.Infrastructure.DownstreamRouteFinder
{
public class UnableToFindDownstreamRouteError : Error
{
public UnableToFindDownstreamRouteError() : base("UnableToFindDownstreamRouteError")
{
}
}
}

View File

@ -1,17 +1,17 @@
using System.Text; using System.Text;
using Ocelot.Library.Infrastructure.UrlMatcher; using Ocelot.Library.Infrastructure.DownstreamRouteFinder;
namespace Ocelot.Library.Infrastructure.UrlTemplateReplacer namespace Ocelot.Library.Infrastructure.UrlTemplateReplacer
{ {
public class DownstreamUrlTemplateVariableReplacer : IDownstreamUrlTemplateVariableReplacer public class DownstreamUrlTemplateVariableReplacer : IDownstreamUrlTemplateVariableReplacer
{ {
public string ReplaceTemplateVariable(UrlMatch urlMatch) public string ReplaceTemplateVariable(DownstreamRoute downstreamRoute)
{ {
var upstreamUrl = new StringBuilder(); var upstreamUrl = new StringBuilder();
upstreamUrl.Append(urlMatch.DownstreamUrlTemplate); upstreamUrl.Append(downstreamRoute.DownstreamUrlTemplate);
foreach (var templateVarAndValue in urlMatch.TemplateVariableNameAndValues) foreach (var templateVarAndValue in downstreamRoute.TemplateVariableNameAndValues)
{ {
upstreamUrl.Replace(templateVarAndValue.TemplateVariableName, templateVarAndValue.TemplateVariableValue); upstreamUrl.Replace(templateVarAndValue.TemplateVariableName, templateVarAndValue.TemplateVariableValue);
} }

View File

@ -1,9 +1,10 @@
using Ocelot.Library.Infrastructure.DownstreamRouteFinder;
using Ocelot.Library.Infrastructure.UrlMatcher; using Ocelot.Library.Infrastructure.UrlMatcher;
namespace Ocelot.Library.Infrastructure.UrlTemplateReplacer namespace Ocelot.Library.Infrastructure.UrlTemplateReplacer
{ {
public interface IDownstreamUrlTemplateVariableReplacer public interface IDownstreamUrlTemplateVariableReplacer
{ {
string ReplaceTemplateVariable(UrlMatch urlMatch); string ReplaceTemplateVariable(DownstreamRoute downstreamRoute);
} }
} }

View File

@ -1,56 +1,62 @@
using System.Net;
using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Library.Infrastructure.UrlMatcher; using Microsoft.Extensions.Options;
using Ocelot.Library.Infrastructure.Configuration;
using Ocelot.Library.Infrastructure.DownstreamRouteFinder;
using Ocelot.Library.Infrastructure.UrlTemplateReplacer; using Ocelot.Library.Infrastructure.UrlTemplateReplacer;
namespace Ocelot.Library.Middleware namespace Ocelot.Library.Middleware
{ {
using System.Net;
using Infrastructure.Configuration;
using Microsoft.Extensions.Options;
public class ProxyMiddleware public class ProxyMiddleware
{ {
private readonly RequestDelegate _next; private readonly RequestDelegate _next;
private readonly IUrlPathToUrlTemplateMatcher _urlMatcher;
private readonly IDownstreamUrlTemplateVariableReplacer _urlReplacer; private readonly IDownstreamUrlTemplateVariableReplacer _urlReplacer;
private readonly IOptions<Configuration> _configuration; private readonly IOptions<Configuration> _configuration;
private readonly IDownstreamRouteFinder _downstreamRouteFinder;
public ProxyMiddleware(RequestDelegate next, public ProxyMiddleware(RequestDelegate next,
IUrlPathToUrlTemplateMatcher urlMatcher, IDownstreamUrlTemplateVariableReplacer urlReplacer,
IDownstreamUrlTemplateVariableReplacer urlReplacer, IOptions<Configuration> configuration) IOptions<Configuration> configuration,
IDownstreamRouteFinder downstreamRouteFinder)
{ {
_next = next; _next = next;
_urlMatcher = urlMatcher;
_urlReplacer = urlReplacer; _urlReplacer = urlReplacer;
_configuration = configuration; _configuration = configuration;
_downstreamRouteFinder = downstreamRouteFinder;
} }
public async Task Invoke(HttpContext context) public async Task Invoke(HttpContext context)
{ {
var upstreamUrlPath = context.Request.Path.ToString(); var upstreamUrlPath = context.Request.Path.ToString();
UrlMatch urlMatch = null; var downstreamRoute = _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath);
foreach (var template in _configuration.Value.ReRoutes) if (downstreamRoute.IsError)
{
urlMatch = _urlMatcher.Match(upstreamUrlPath, template.UpstreamTemplate);
if (urlMatch.Match)
{
break;
}
}
if (urlMatch == null || !urlMatch.Match)
{ {
context.Response.StatusCode = (int)HttpStatusCode.NotFound; context.Response.StatusCode = (int)HttpStatusCode.NotFound;
return; return;
} }
var downstreamUrl = _urlReplacer.ReplaceTemplateVariable(urlMatch); var downstreamUrl = _urlReplacer.ReplaceTemplateVariable(downstreamRoute.Data);
//make a http request to this endpoint...maybe bring in a library //make a http request to this endpoint...maybe bring in a library
using (var httpClient = new HttpClient())
{
var httpMethod = new HttpMethod(context.Request.Method);
var httpRequestMessage = new HttpRequestMessage(httpMethod, downstreamUrl);
var response = await httpClient.SendAsync(httpRequestMessage);
if (!response.IsSuccessStatusCode)
{
context.Response.StatusCode = (int)response.StatusCode;
return;
}
await context.Response.WriteAsync(await response.Content.ReadAsStringAsync());
}
await _next.Invoke(context); await _next.Invoke(context);
} }

View File

@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Ocelot.Library.Infrastructure.DownstreamRouteFinder;
using Ocelot.Library.Middleware; using Ocelot.Library.Middleware;
namespace Ocelot namespace Ocelot
@ -36,6 +37,7 @@ namespace Ocelot
// Add framework services. // Add framework services.
services.AddSingleton<IUrlPathToUrlTemplateMatcher, UrlPathToUrlTemplateMatcher>(); services.AddSingleton<IUrlPathToUrlTemplateMatcher, UrlPathToUrlTemplateMatcher>();
services.AddSingleton<IDownstreamUrlTemplateVariableReplacer, DownstreamUrlTemplateVariableReplacer>(); services.AddSingleton<IDownstreamUrlTemplateVariableReplacer, DownstreamUrlTemplateVariableReplacer>();
services.AddSingleton<IDownstreamRouteFinder, DownstreamRouteFinder>();
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

View File

@ -90,7 +90,7 @@ namespace Ocelot.AcceptanceTests
private void WhenIRequestTheUrlOnTheApiGateway(string url) private void WhenIRequestTheUrlOnTheApiGateway(string url)
{ {
_response = _client.GetAsync("/").Result; _response = _client.GetAsync(url).Result;
} }
private void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode) private void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode)

View File

@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Moq;
using Ocelot.Library.Infrastructure.Configuration;
using Ocelot.Library.Infrastructure.DownstreamRouteFinder;
using Ocelot.Library.Infrastructure.Responses;
using Ocelot.Library.Infrastructure.UrlMatcher;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests
{
public class DownstreamRouteFinderTests
{
private readonly IDownstreamRouteFinder _downstreamRouteFinder;
private readonly Mock<IOptions<Configuration>> _mockConfig;
private readonly Mock<IUrlPathToUrlTemplateMatcher> _mockMatcher;
private string _upstreamUrlPath;
private Response<DownstreamRoute> _result;
private Response<DownstreamRoute> _response;
private Configuration _configuration;
private UrlMatch _match;
public DownstreamRouteFinderTests()
{
_mockConfig = new Mock<IOptions<Configuration>>();
_mockMatcher = new Mock<IUrlPathToUrlTemplateMatcher>();
_downstreamRouteFinder = new DownstreamRouteFinder(_mockConfig.Object, _mockMatcher.Object);
}
[Fact]
public void should_return_route()
{
this.Given(x => x.GivenThereIsAnUpstreamUrlPath("somePath"))
.And(x => x.GivenTheConfigurationIs(new Configuration {
ReRoutes = new List<ReRoute>
{
new ReRoute()
{
UpstreamTemplate = "somePath",
DownstreamTemplate = "somPath"
}
}
}))
.And(x => x.GivenTheUrlMatcherReturns(new UrlMatch(true, new List<TemplateVariableNameAndValue>(), "somePath")))
.When(x => x.WhenICallTheFinder())
.Then(
x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List<TemplateVariableNameAndValue>(), "somePath")))
.And(x => x.ThenTheUrlMatcherIsCalledCorrectly())
.BDDfy();
}
[Fact]
public void should_not_return_route()
{
this.Given(x => x.GivenThereIsAnUpstreamUrlPath("somePath"))
.And(x => x.GivenTheConfigurationIs(new Configuration
{
ReRoutes = new List<ReRoute>
{
new ReRoute()
{
UpstreamTemplate = "somePath",
DownstreamTemplate = "somPath"
}
}
}))
.And(x => x.GivenTheUrlMatcherReturns(new UrlMatch(false, new List<TemplateVariableNameAndValue>(), null)))
.When(x => x.WhenICallTheFinder())
.Then(
x => x.ThenAnErrorResponseIsReturned())
.And(x => x.ThenTheUrlMatcherIsCalledCorrectly())
.BDDfy();
}
private void ThenAnErrorResponseIsReturned()
{
_result.IsError.ShouldBeTrue();
}
private void ThenTheUrlMatcherIsCalledCorrectly()
{
_mockMatcher
.Verify(x => x.Match(_upstreamUrlPath, _configuration.ReRoutes[0].UpstreamTemplate), Times.Once);
}
private void GivenTheUrlMatcherReturns(UrlMatch match)
{
_match = match;
_mockMatcher
.Setup(x => x.Match(It.IsAny<string>(), It.IsAny<string>()))
.Returns(_match);
}
private void GivenTheConfigurationIs(Configuration configuration)
{
_configuration = configuration;
_mockConfig
.Setup(x => x.Value)
.Returns(_configuration);
}
private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath)
{
_upstreamUrlPath = upstreamUrlPath;
}
private void WhenICallTheFinder()
{
_result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath);
}
private void ThenTheFollowingIsReturned(DownstreamRoute expected)
{
_result.Data.DownstreamUrlTemplate.ShouldBe(expected.DownstreamUrlTemplate);
for (int i = 0; i < _result.Data.TemplateVariableNameAndValues.Count; i++)
{
_result.Data.TemplateVariableNameAndValues[i].TemplateVariableName.ShouldBe(
expected.TemplateVariableNameAndValues[i].TemplateVariableName);
_result.Data.TemplateVariableNameAndValues[i].TemplateVariableValue.ShouldBe(
expected.TemplateVariableNameAndValues[i].TemplateVariableValue);
}
_result.IsError.ShouldBeFalse();
}
}
}

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Ocelot.Library.Infrastructure.DownstreamRouteFinder;
using Ocelot.Library.Infrastructure.UrlMatcher; using Ocelot.Library.Infrastructure.UrlMatcher;
using Ocelot.Library.Infrastructure.UrlTemplateReplacer; using Ocelot.Library.Infrastructure.UrlTemplateReplacer;
using Shouldly; using Shouldly;
@ -10,7 +11,7 @@ namespace Ocelot.UnitTests
public class UpstreamUrlPathTemplateVariableReplacerTests public class UpstreamUrlPathTemplateVariableReplacerTests
{ {
private UrlMatch _urlMatch; private DownstreamRoute _downstreamRoute;
private string _result; private string _result;
private readonly IDownstreamUrlTemplateVariableReplacer _downstreamUrlPathReplacer; private readonly IDownstreamUrlTemplateVariableReplacer _downstreamUrlPathReplacer;
@ -22,7 +23,7 @@ namespace Ocelot.UnitTests
[Fact] [Fact]
public void can_replace_no_template_variables() public void can_replace_no_template_variables()
{ {
this.Given(x => x.GivenThereIsAUrlMatch(new UrlMatch(true, new List<TemplateVariableNameAndValue>(), ""))) this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List<TemplateVariableNameAndValue>(), "")))
.When(x => x.WhenIReplaceTheTemplateVariables()) .When(x => x.WhenIReplaceTheTemplateVariables())
.Then(x => x.ThenTheDownstreamUrlPathIsReturned("")) .Then(x => x.ThenTheDownstreamUrlPathIsReturned(""))
.BDDfy(); .BDDfy();
@ -31,7 +32,7 @@ namespace Ocelot.UnitTests
[Fact] [Fact]
public void can_replace_no_template_variables_with_slash() public void can_replace_no_template_variables_with_slash()
{ {
this.Given(x => x.GivenThereIsAUrlMatch(new UrlMatch(true, new List<TemplateVariableNameAndValue>(), "/"))) this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List<TemplateVariableNameAndValue>(), "/")))
.When(x => x.WhenIReplaceTheTemplateVariables()) .When(x => x.WhenIReplaceTheTemplateVariables())
.Then(x => x.ThenTheDownstreamUrlPathIsReturned("/")) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("/"))
.BDDfy(); .BDDfy();
@ -40,7 +41,7 @@ namespace Ocelot.UnitTests
[Fact] [Fact]
public void can_replace_url_no_slash() public void can_replace_url_no_slash()
{ {
this.Given(x => x.GivenThereIsAUrlMatch(new UrlMatch(true, new List<TemplateVariableNameAndValue>(), "api"))) this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List<TemplateVariableNameAndValue>(), "api")))
.When(x => x.WhenIReplaceTheTemplateVariables()) .When(x => x.WhenIReplaceTheTemplateVariables())
.Then(x => x.ThenTheDownstreamUrlPathIsReturned("api")) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api"))
.BDDfy(); .BDDfy();
@ -49,7 +50,7 @@ namespace Ocelot.UnitTests
[Fact] [Fact]
public void can_replace_url_one_slash() public void can_replace_url_one_slash()
{ {
this.Given(x => x.GivenThereIsAUrlMatch(new UrlMatch(true, new List<TemplateVariableNameAndValue>(), "api/"))) this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List<TemplateVariableNameAndValue>(), "api/")))
.When(x => x.WhenIReplaceTheTemplateVariables()) .When(x => x.WhenIReplaceTheTemplateVariables())
.Then(x => x.ThenTheDownstreamUrlPathIsReturned("api/")) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api/"))
.BDDfy(); .BDDfy();
@ -58,7 +59,7 @@ namespace Ocelot.UnitTests
[Fact] [Fact]
public void can_replace_url_multiple_slash() public void can_replace_url_multiple_slash()
{ {
this.Given(x => x.GivenThereIsAUrlMatch(new UrlMatch(true, new List<TemplateVariableNameAndValue>(), "api/product/products/"))) this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List<TemplateVariableNameAndValue>(), "api/product/products/")))
.When(x => x.WhenIReplaceTheTemplateVariables()) .When(x => x.WhenIReplaceTheTemplateVariables())
.Then(x => x.ThenTheDownstreamUrlPathIsReturned("api/product/products/")) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api/product/products/"))
.BDDfy(); .BDDfy();
@ -72,7 +73,7 @@ namespace Ocelot.UnitTests
new TemplateVariableNameAndValue("{productId}", "1") new TemplateVariableNameAndValue("{productId}", "1")
}; };
this.Given(x => x.GivenThereIsAUrlMatch(new UrlMatch(true, templateVariables, "productservice/products/{productId}/"))) this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, "productservice/products/{productId}/")))
.When(x => x.WhenIReplaceTheTemplateVariables()) .When(x => x.WhenIReplaceTheTemplateVariables())
.Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/")) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/"))
.BDDfy(); .BDDfy();
@ -86,7 +87,7 @@ namespace Ocelot.UnitTests
new TemplateVariableNameAndValue("{productId}", "1") new TemplateVariableNameAndValue("{productId}", "1")
}; };
this.Given(x => x.GivenThereIsAUrlMatch(new UrlMatch(true, templateVariables, "productservice/products/{productId}/variants"))) this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, "productservice/products/{productId}/variants")))
.When(x => x.WhenIReplaceTheTemplateVariables()) .When(x => x.WhenIReplaceTheTemplateVariables())
.Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/variants")) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/variants"))
.BDDfy(); .BDDfy();
@ -101,7 +102,7 @@ namespace Ocelot.UnitTests
new TemplateVariableNameAndValue("{variantId}", "12") new TemplateVariableNameAndValue("{variantId}", "12")
}; };
this.Given(x => x.GivenThereIsAUrlMatch(new UrlMatch(true, templateVariables, "productservice/products/{productId}/variants/{variantId}"))) this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, "productservice/products/{productId}/variants/{variantId}")))
.When(x => x.WhenIReplaceTheTemplateVariables()) .When(x => x.WhenIReplaceTheTemplateVariables())
.Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/variants/12")) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/variants/12"))
.BDDfy(); .BDDfy();
@ -117,20 +118,20 @@ namespace Ocelot.UnitTests
new TemplateVariableNameAndValue("{categoryId}", "34") new TemplateVariableNameAndValue("{categoryId}", "34")
}; };
this.Given(x => x.GivenThereIsAUrlMatch(new UrlMatch(true, templateVariables, "productservice/category/{categoryId}/products/{productId}/variants/{variantId}"))) this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, "productservice/category/{categoryId}/products/{productId}/variants/{variantId}")))
.When(x => x.WhenIReplaceTheTemplateVariables()) .When(x => x.WhenIReplaceTheTemplateVariables())
.Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/category/34/products/1/variants/12")) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/category/34/products/1/variants/12"))
.BDDfy(); .BDDfy();
} }
private void GivenThereIsAUrlMatch(UrlMatch urlMatch) private void GivenThereIsAUrlMatch(DownstreamRoute downstreamRoute)
{ {
_urlMatch = urlMatch; _downstreamRoute = downstreamRoute;
} }
private void WhenIReplaceTheTemplateVariables() private void WhenIReplaceTheTemplateVariables()
{ {
_result = _downstreamUrlPathReplacer.ReplaceTemplateVariable(_urlMatch); _result = _downstreamUrlPathReplacer.ReplaceTemplateVariable(_downstreamRoute);
} }
private void ThenTheDownstreamUrlPathIsReturned(string expected) private void ThenTheDownstreamUrlPathIsReturned(string expected)

View File

@ -24,7 +24,8 @@
"dotnet-test-xunit": "2.2.0-preview2-build1029", "dotnet-test-xunit": "2.2.0-preview2-build1029",
"Shouldly": "2.8.0", "Shouldly": "2.8.0",
"TestStack.BDDfy": "4.3.1", "TestStack.BDDfy": "4.3.1",
"YamlDotNet": "3.9.0" "YamlDotNet": "3.9.0",
"Moq": "4.6.38-alpha"
}, },
"frameworks": { "frameworks": {