diff --git a/docs/features/routing.rst b/docs/features/routing.rst index 0fc1a1a29..119f63e85 100644 --- a/docs/features/routing.rst +++ b/docs/features/routing.rst @@ -2,7 +2,7 @@ Routing ======= Ocelot's primary functionality is to take incoming http requests and forward them on to a downstream service. Ocelot currently only supports this in the form of another http request (in the future -this could be any transport mechanism). +this could be any transport mechanism). Ocelot's describes the routing of one request to another as a Route. In order to get anything working in Ocelot you need to set up a Route in the configuration. @@ -251,13 +251,41 @@ A sample configuration might look like the following: { "Routes": [ { + "DownstreamPathTemplate": "/api/posts", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "server1", + "Port": 80, + } + ], + "UpstreamPathTemplate": "/posts", + "UpstreamHttpMethod": [ "Put", "Delete" ] "UpstreamHeaderRoutingOptions": { "Headers": { - "X-API-Version": [ "1", "2" ], - "X-Tennant-Id": [ "tennantId" ] + "X-API-Version": [ "1" ], + "X-Tenant-Id": [ "tenantId" ] }, "TriggerOn": "all" } + }, + { + "DownstreamPathTemplate": "/api/posts", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "server2", + "Port": 80, + } + ], + "UpstreamPathTemplate": "/posts", + "UpstreamHttpMethod": [ "Put", "Delete" ] + "UpstreamHeaderRoutingOptions": { + "Headers": { + "X-API-Version": [ "2" ] + }, + "TriggerOn": "any" + } } ] } diff --git a/src/Ocelot/Configuration/Creator/IUpstreamHeaderRoutingOptionsCreator.cs b/src/Ocelot/Configuration/Creator/IUpstreamHeaderRoutingOptionsCreator.cs index 8c965d08f..bd26ad130 100644 --- a/src/Ocelot/Configuration/Creator/IUpstreamHeaderRoutingOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/IUpstreamHeaderRoutingOptionsCreator.cs @@ -1,9 +1,8 @@ using Ocelot.Configuration.File; -namespace Ocelot.Configuration.Creator +namespace Ocelot.Configuration.Creator; + +public interface IUpstreamHeaderRoutingOptionsCreator { - public interface IUpstreamHeaderRoutingOptionsCreator - { - UpstreamHeaderRoutingOptions Create(FileUpstreamHeaderRoutingOptions options); - } + UpstreamHeaderRoutingOptions Create(FileUpstreamHeaderRoutingOptions options); } diff --git a/src/Ocelot/Configuration/Creator/UpstreamHeaderRoutingOptionsCreator.cs b/src/Ocelot/Configuration/Creator/UpstreamHeaderRoutingOptionsCreator.cs index 730d0f654..8f4416a5f 100644 --- a/src/Ocelot/Configuration/Creator/UpstreamHeaderRoutingOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/UpstreamHeaderRoutingOptionsCreator.cs @@ -1,25 +1,27 @@ using System; using System.Linq; -using System.Collections.Generic; using Ocelot.Configuration.File; -namespace Ocelot.Configuration.Creator +namespace Ocelot.Configuration.Creator; + +public class UpstreamHeaderRoutingOptionsCreator : IUpstreamHeaderRoutingOptionsCreator { - public class UpstreamHeaderRoutingOptionsCreator : IUpstreamHeaderRoutingOptionsCreator + public UpstreamHeaderRoutingOptions Create(FileUpstreamHeaderRoutingOptions options) { - public UpstreamHeaderRoutingOptions Create(FileUpstreamHeaderRoutingOptions options) + var mode = UpstreamHeaderRoutingTriggerMode.Any; + if (options.TriggerOn.Length > 0) { - UpstreamHeaderRoutingTriggerMode mode = UpstreamHeaderRoutingTriggerMode.Any; - if (options.TriggerOn.Length > 0) - { - mode = Enum.Parse(options.TriggerOn, true); - } + mode = Enum.Parse(options.TriggerOn, true); + } - Dictionary> headers = options.Headers.ToDictionary( - kv => kv.Key.ToLowerInvariant(), - kv => new HashSet(kv.Value.Select(v => v.ToLowerInvariant()))); + // Keys are converted to uppercase as apparently that is the preferred + // approach according to https://learn.microsoft.com/en-us/dotnet/standard/base-types/best-practices-strings + // Values are left untouched but value comparison at runtime is done in + // a case-insensitive manner by using the appropriate StringComparer. + var headers = options.Headers.ToDictionary( + kv => kv.Key.ToUpperInvariant(), + kv => kv.Value); - return new UpstreamHeaderRoutingOptions(headers, mode); - } + return new UpstreamHeaderRoutingOptions(headers, mode); } } diff --git a/src/Ocelot/Configuration/File/FileUpstreamHeaderRoutingOptions.cs b/src/Ocelot/Configuration/File/FileUpstreamHeaderRoutingOptions.cs index 7e6a63a5e..367409aa2 100644 --- a/src/Ocelot/Configuration/File/FileUpstreamHeaderRoutingOptions.cs +++ b/src/Ocelot/Configuration/File/FileUpstreamHeaderRoutingOptions.cs @@ -1,17 +1,10 @@ using System.Collections.Generic; -namespace Ocelot.Configuration.File -{ - public class FileUpstreamHeaderRoutingOptions - { - public FileUpstreamHeaderRoutingOptions() - { - Headers = new Dictionary>(); - TriggerOn = string.Empty; - } +namespace Ocelot.Configuration.File; - public IDictionary> Headers { get; set; } +public class FileUpstreamHeaderRoutingOptions +{ + public IDictionary> Headers { get; set; } = new Dictionary>(); - public string TriggerOn { get; set; } - } + public string TriggerOn { get; set; } = string.Empty; } diff --git a/src/Ocelot/Configuration/UpstreamHeaderRoutingOptions.cs b/src/Ocelot/Configuration/UpstreamHeaderRoutingOptions.cs index 576dae05d..90e2bc22f 100644 --- a/src/Ocelot/Configuration/UpstreamHeaderRoutingOptions.cs +++ b/src/Ocelot/Configuration/UpstreamHeaderRoutingOptions.cs @@ -1,19 +1,18 @@ using System.Collections.Generic; -namespace Ocelot.Configuration +namespace Ocelot.Configuration; + +public class UpstreamHeaderRoutingOptions { - public class UpstreamHeaderRoutingOptions + public UpstreamHeaderRoutingOptions(IReadOnlyDictionary> headers, UpstreamHeaderRoutingTriggerMode mode) { - public UpstreamHeaderRoutingOptions(IReadOnlyDictionary> headers, UpstreamHeaderRoutingTriggerMode mode) - { - Headers = new UpstreamRoutingHeaders(headers); - Mode = mode; - } + Headers = new UpstreamRoutingHeaders(headers); + Mode = mode; + } - public bool Enabled() => Headers.Any(); + public bool Enabled() => Headers.Any(); - public UpstreamRoutingHeaders Headers { get; } + public UpstreamRoutingHeaders Headers { get; } - public UpstreamHeaderRoutingTriggerMode Mode { get; } - } + public UpstreamHeaderRoutingTriggerMode Mode { get; } } diff --git a/src/Ocelot/Configuration/UpstreamHeaderRoutingTriggerMode.cs b/src/Ocelot/Configuration/UpstreamHeaderRoutingTriggerMode.cs index 8596ae7f1..47a3e636e 100644 --- a/src/Ocelot/Configuration/UpstreamHeaderRoutingTriggerMode.cs +++ b/src/Ocelot/Configuration/UpstreamHeaderRoutingTriggerMode.cs @@ -1,8 +1,7 @@ -namespace Ocelot.Configuration +namespace Ocelot.Configuration; + +public enum UpstreamHeaderRoutingTriggerMode : byte { - public enum UpstreamHeaderRoutingTriggerMode - { - Any = 0, - All = 1, - } + Any, + All, } diff --git a/src/Ocelot/Configuration/UpstreamRoutingHeaders.cs b/src/Ocelot/Configuration/UpstreamRoutingHeaders.cs index 119155c1e..0684b4ccc 100644 --- a/src/Ocelot/Configuration/UpstreamRoutingHeaders.cs +++ b/src/Ocelot/Configuration/UpstreamRoutingHeaders.cs @@ -1,70 +1,65 @@ +using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; -namespace Ocelot.Configuration +namespace Ocelot.Configuration; + +public class UpstreamRoutingHeaders { - public class UpstreamRoutingHeaders - { - public IReadOnlyDictionary> Headers { get; } + public IReadOnlyDictionary> Headers { get; } - public UpstreamRoutingHeaders(IReadOnlyDictionary> headers) - { - Headers = headers; - } + public UpstreamRoutingHeaders(IReadOnlyDictionary> headers) + { + Headers = headers; + } - public bool Any() => Headers.Any(); + public bool Any() => Headers.Any(); - public bool HasAnyOf(IHeaderDictionary requestHeaders) + public bool HasAnyOf(IHeaderDictionary requestHeaders) + { + IHeaderDictionary normalizedHeaders = NormalizeHeaderNames(requestHeaders); + foreach (var h in Headers) { - IHeaderDictionary lowerCaseHeaders = GetLowerCaseHeaders(requestHeaders); - foreach (KeyValuePair> h in Headers) + if (normalizedHeaders.TryGetValue(h.Key, out var values) && + h.Value.Intersect(values, StringComparer.OrdinalIgnoreCase).Any()) { - if (lowerCaseHeaders.TryGetValue(h.Key, out var values)) - { - HashSet requestHeaderValues = new(values); - if (h.Value.Overlaps(requestHeaderValues)) - { - return true; - } - } + return true; } - - return false; } - public bool HasAllOf(IHeaderDictionary requestHeaders) + return false; + } + + public bool HasAllOf(IHeaderDictionary requestHeaders) + { + IHeaderDictionary normalizedHeaders = NormalizeHeaderNames(requestHeaders); + foreach (var h in Headers) { - IHeaderDictionary lowerCaseHeaders = GetLowerCaseHeaders(requestHeaders); - foreach (KeyValuePair> h in Headers) + if (!normalizedHeaders.TryGetValue(h.Key, out var values)) { - if (!lowerCaseHeaders.TryGetValue(h.Key, out var values)) - { - return false; - } - - HashSet requestHeaderValues = new(values); - if (!h.Value.Overlaps(requestHeaderValues)) - { - return false; - } + return false; } - return true; - } - - private static IHeaderDictionary GetLowerCaseHeaders(IHeaderDictionary headers) - { - IHeaderDictionary lowerCaseHeaders = new HeaderDictionary(); - foreach (KeyValuePair kv in headers) + if (!h.Value.Intersect(values, StringComparer.OrdinalIgnoreCase).Any()) { - string key = kv.Key.ToLowerInvariant(); - StringValues values = new(kv.Value.Select(v => v.ToLowerInvariant()).ToArray()); - lowerCaseHeaders.Add(key, values); + return false; } + } - return lowerCaseHeaders; + return true; + } + + private static IHeaderDictionary NormalizeHeaderNames(IHeaderDictionary headers) + { + var upperCaseHeaders = new HeaderDictionary(); + foreach (KeyValuePair kv in headers) + { + var key = kv.Key.ToUpperInvariant(); + upperCaseHeaders.Add(key, kv.Value); } + + return upperCaseHeaders; } } diff --git a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs index 7562e21e2..991aacc91 100644 --- a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs +++ b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs @@ -127,7 +127,9 @@ private static bool IsNotDuplicateIn(FileRoute route, var matchingRoutes = routes .Where(r => r.UpstreamPathTemplate == route.UpstreamPathTemplate && r.UpstreamHost == route.UpstreamHost - && AreDuplicateUpstreamRoutingHeaders(route, r)) + && AreDuplicateUpstreamRoutingHeaders( + route.UpstreamHeaderRoutingOptions.Headers, + r.UpstreamHeaderRoutingOptions.Headers)) .ToList(); if (matchingRoutes.Count == 1) @@ -172,45 +174,39 @@ private static bool IsNotDuplicateIn(FileAggregateRoute route, return matchingRoutes.Count() <= 1; } - private static bool AreDuplicateUpstreamRoutingHeaders(FileRoute first, FileRoute second) + private static bool AreDuplicateUpstreamRoutingHeaders( + IDictionary> first, + IDictionary> second) { - if (!first.UpstreamHeaderRoutingOptions.Headers.Any() && !second.UpstreamHeaderRoutingOptions.Headers.Any()) + if (!first.Any() && !second.Any()) { return true; } - if (first.UpstreamHeaderRoutingOptions.Headers.Any() ^ second.UpstreamHeaderRoutingOptions.Headers.Any()) + if (first.Any() ^ second.Any()) { return false; } - ISet firstKeySet = first.UpstreamHeaderRoutingOptions.Headers.Keys - .Select(k => k.ToLowerInvariant()) - .ToHashSet(); - ISet secondKeySet = second.UpstreamHeaderRoutingOptions.Headers.Keys - .Select(k => k.ToLowerInvariant()) - .ToHashSet(); - if (!firstKeySet.Overlaps(secondKeySet)) + var firstKeySet = first.Keys + .Select(k => k.ToUpperInvariant()) + .ToArray(); + var secondKeySet = second.Keys + .Select(k => k.ToUpperInvariant()) + .ToArray(); + if (!firstKeySet.Intersect(secondKeySet).Any()) { return false; } - foreach (var (key, values) in first.UpstreamHeaderRoutingOptions.Headers) + foreach (var (key, firstValues) in first) { - IDictionary> secondHeaders = second.UpstreamHeaderRoutingOptions.Headers; - if (!secondHeaders.TryGetValue(key, out IList secondHeaderValues)) + if (!second.TryGetValue(key, out var secondValues)) { continue; } - ISet firstHeaderValuesLowerCase = values - .Select(v => v.ToLowerInvariant()) - .ToHashSet(); - ISet secondHeaderValuesLowerCase = secondHeaderValues - .Select(v => v.ToLowerInvariant()) - .ToHashSet(); - - if (firstHeaderValuesLowerCase.Overlaps(secondHeaderValuesLowerCase)) + if (firstValues.Intersect(secondValues, StringComparer.OrdinalIgnoreCase).Any()) { return true; } diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index d5c545e34..da1bb9314 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Http; using Ocelot.Configuration; @@ -57,30 +58,16 @@ private static bool RouteIsApplicableToThisRequest(Route route, string httpMetho { return (route.UpstreamHttpMethod.Count == 0 || RouteHasHttpMethod(route, httpMethod)) && (string.IsNullOrEmpty(route.UpstreamHost) || route.UpstreamHost == upstreamHost) && - (route.UpstreamHeaderRoutingOptions == null || !route.UpstreamHeaderRoutingOptions.Enabled() || RouteHasRequiredUpstreamHeaders(route, requestHeaders)); + (!(route.UpstreamHeaderRoutingOptions?.Enabled() ?? false) || RouteHasRequiredUpstreamHeaders(route, requestHeaders)); } - private static bool RouteHasHttpMethod(Route route, string httpMethod) - { - httpMethod = httpMethod.ToLower(); - return route.UpstreamHttpMethod.Select(x => x.Method.ToLower()).Contains(httpMethod); - } + private static bool RouteHasHttpMethod(Route route, string httpMethod) => + route.UpstreamHttpMethod.Select(x => x.Method).Contains(httpMethod, StringComparer.OrdinalIgnoreCase); - private static bool RouteHasRequiredUpstreamHeaders(Route route, IHeaderDictionary requestHeaders) - { - bool result = false; - switch (route.UpstreamHeaderRoutingOptions.Mode) - { - case UpstreamHeaderRoutingTriggerMode.Any: - result = route.UpstreamHeaderRoutingOptions.Headers.HasAnyOf(requestHeaders); - break; - case UpstreamHeaderRoutingTriggerMode.All: - result = route.UpstreamHeaderRoutingOptions.Headers.HasAllOf(requestHeaders); - break; - } - - return result; - } + private static bool RouteHasRequiredUpstreamHeaders(Route route, IHeaderDictionary requestHeaders) => + route.UpstreamHeaderRoutingOptions.Mode == UpstreamHeaderRoutingTriggerMode.Any + ? route.UpstreamHeaderRoutingOptions.Headers.HasAnyOf(requestHeaders) + : route.UpstreamHeaderRoutingOptions.Headers.HasAllOf(requestHeaders); private DownstreamRouteHolder GetPlaceholderNamesAndValues(string path, string query, Route route) { diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderRoutingOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderRoutingOptionsCreatorTests.cs index 942be3843..178a83ac2 100644 --- a/test/Ocelot.UnitTests/Configuration/UpstreamHeaderRoutingOptionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/UpstreamHeaderRoutingOptionsCreatorTests.cs @@ -7,65 +7,64 @@ using TestStack.BDDfy; using Shouldly; -namespace Ocelot.UnitTests.Configuration +namespace Ocelot.UnitTests.Configuration; + +public class UpstreamHeaderRoutingOptionsCreatorTests { - public class UpstreamHeaderRoutingOptionsCreatorTests - { - private FileUpstreamHeaderRoutingOptions _fileUpstreamHeaderRoutingOptions; - private IUpstreamHeaderRoutingOptionsCreator _creator; - private UpstreamHeaderRoutingOptions _upstreamHeaderRoutingOptions; + private FileUpstreamHeaderRoutingOptions _fileUpstreamHeaderRoutingOptions; + private IUpstreamHeaderRoutingOptionsCreator _creator; + private UpstreamHeaderRoutingOptions _upstreamHeaderRoutingOptions; - public UpstreamHeaderRoutingOptionsCreatorTests() - { - _creator = new UpstreamHeaderRoutingOptionsCreator(); - } + public UpstreamHeaderRoutingOptionsCreatorTests() + { + _creator = new UpstreamHeaderRoutingOptionsCreator(); + } - [Fact] - public void should_create_upstream_routing_header_options() - { - UpstreamHeaderRoutingOptions expected = new UpstreamHeaderRoutingOptions( - headers: new Dictionary>() - { - { "header1", new HashSet() { "value1", "value2" }}, - { "header2", new HashSet() { "value3" }}, - }, - mode: UpstreamHeaderRoutingTriggerMode.All - ); + [Fact] + public void should_create_upstream_routing_header_options() + { + UpstreamHeaderRoutingOptions expected = new( + headers: new Dictionary>() + { + { "HEADER1", new[] { "Value1", "Value2" }}, + { "HEADER2", new[] { "Value3" }}, + }, + mode: UpstreamHeaderRoutingTriggerMode.All + ); - this.Given(_ => GivenTheseFileUpstreamHeaderRoutingOptions()) - .When(_ => WhenICreate()) - .Then(_ => ThenTheCreatedMatchesThis(expected)) - .BDDfy(); - } + this.Given(_ => GivenTheseFileUpstreamHeaderRoutingOptions()) + .When(_ => WhenICreate()) + .Then(_ => ThenTheCreatedMatchesThis(expected)) + .BDDfy(); + } - private void GivenTheseFileUpstreamHeaderRoutingOptions() + private void GivenTheseFileUpstreamHeaderRoutingOptions() + { + _fileUpstreamHeaderRoutingOptions = new FileUpstreamHeaderRoutingOptions() { - _fileUpstreamHeaderRoutingOptions = new FileUpstreamHeaderRoutingOptions() + Headers = new Dictionary>() { - Headers = new Dictionary>() - { - { "Header1", new List() { "Value1", "Value2" }}, - { "Header2", new List() { "Value3" }}, - }, - TriggerOn = "all", - }; - } + { "Header1", new[] { "Value1", "Value2" }}, + { "Header2", new[] { "Value3" }}, + }, + TriggerOn = "all", + }; + } - private void WhenICreate() - { - _upstreamHeaderRoutingOptions = _creator.Create(_fileUpstreamHeaderRoutingOptions); - } + private void WhenICreate() + { + _upstreamHeaderRoutingOptions = _creator.Create(_fileUpstreamHeaderRoutingOptions); + } - private void ThenTheCreatedMatchesThis(UpstreamHeaderRoutingOptions expected) + private void ThenTheCreatedMatchesThis(UpstreamHeaderRoutingOptions expected) + { + _upstreamHeaderRoutingOptions.Headers.Headers.Count.ShouldBe(expected.Headers.Headers.Count); + foreach (var pair in _upstreamHeaderRoutingOptions.Headers.Headers) { - _upstreamHeaderRoutingOptions.Headers.Headers.Count.ShouldBe(expected.Headers.Headers.Count); - foreach (KeyValuePair> pair in _upstreamHeaderRoutingOptions.Headers.Headers) - { - expected.Headers.Headers.TryGetValue(pair.Key, out var expectedValue).ShouldBe(true); - expectedValue.SetEquals(pair.Value).ShouldBe(true); - } - - _upstreamHeaderRoutingOptions.Mode.ShouldBe(expected.Mode); + expected.Headers.Headers.TryGetValue(pair.Key, out var expectedValue).ShouldBe(true); + expectedValue.ShouldBeEquivalentTo(pair.Value); } + + _upstreamHeaderRoutingOptions.Mode.ShouldBe(expected.Mode); } } diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamRoutingHeadersTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamRoutingHeadersTests.cs index 87eb5306a..18bb115ad 100644 --- a/test/Ocelot.UnitTests/Configuration/UpstreamRoutingHeadersTests.cs +++ b/test/Ocelot.UnitTests/Configuration/UpstreamRoutingHeadersTests.cs @@ -6,137 +6,136 @@ using Shouldly; using Ocelot.Configuration; -namespace Ocelot.UnitTests.Configuration +namespace Ocelot.UnitTests.Configuration; + +public class UpstreamRoutingHeadersTests { - public class UpstreamRoutingHeadersTests + private IReadOnlyDictionary> _headersDictionary; + private UpstreamRoutingHeaders _upstreamRoutingHeaders; + private IHeaderDictionary _requestHeaders; + + [Fact] + public void should_create_empty_headers() { - private Dictionary> _headersDictionary; - private UpstreamRoutingHeaders _upstreamRoutingHeaders; - private IHeaderDictionary _requestHeaders; + this.Given(_ => GivenEmptyHeaderDictionary()) + .When(_ => WhenICreate()) + .Then(_ => ThenAnyIs(false)) + .BDDfy(); + } - [Fact] - public void should_create_empty_headers() - { - this.Given(_ => GivenEmptyHeaderDictionary()) - .When(_ => WhenICreate()) - .Then(_ => ThenAnyIs(false)) - .BDDfy(); - } - - [Fact] - public void should_create_preset_headers() - { - this.Given(_ => GivenPresetHeaderDictionary()) - .When(_ => WhenICreate()) - .Then(_ => ThenAnyIs(true)) - .BDDfy(); - } - - [Fact] - public void should_not_match_mismatching_request_headers() - { - this.Given(_ => GivenPresetHeaderDictionary()) - .And(_ => AndGivenMismatchingRequestHeaders()) - .When(_ => WhenICreate()) - .Then(_ => ThenHasAnyOfIs(false)) - .And(_ => ThenHasAllOfIs(false)) - .BDDfy(); - } - - [Fact] - public void should_not_match_matching_header_with_mismatching_value() - { - this.Given(_ => GivenPresetHeaderDictionary()) - .And(_ => AndGivenOneMatchingHeaderWithMismatchingValue()) - .When(_ => WhenICreate()) - .Then(_ => ThenHasAnyOfIs(false)) - .And(_ => ThenHasAllOfIs(false)) - .BDDfy(); - } - - [Fact] - public void should_match_any_header_not_all() - { - this.Given(_ => GivenPresetHeaderDictionary()) - .And(_ => AndGivenOneMatchingHeaderWithMatchingValue()) - .When(_ => WhenICreate()) - .Then(_ => ThenHasAnyOfIs(true)) - .And(_ => ThenHasAllOfIs(false)) - .BDDfy(); - } - - [Fact] - public void should_match_any_and_all_headers() - { - this.Given(_ => GivenPresetHeaderDictionary()) - .And(_ => AndGivenTwoMatchingHeadersWithMatchingValues()) - .When(_ => WhenICreate()) - .Then(_ => ThenHasAnyOfIs(true)) - .And(_ => ThenHasAllOfIs(true)) - .BDDfy(); - } - - private void GivenEmptyHeaderDictionary() - { - _headersDictionary = new Dictionary>(); - } + [Fact] + public void should_create_preset_headers() + { + this.Given(_ => GivenPresetHeaderDictionary()) + .When(_ => WhenICreate()) + .Then(_ => ThenAnyIs(true)) + .BDDfy(); + } - private void GivenPresetHeaderDictionary() - { - _headersDictionary = new Dictionary>() - { - { "testheader1", new HashSet() { "testheader1value1", "testheader1value2" } }, - { "testheader2", new HashSet() { "testheader1Value1", "testheader2value2" } }, - }; - } - - private void AndGivenMismatchingRequestHeaders() - { - _requestHeaders = new HeaderDictionary() { - { "someHeader", new StringValues(new []{ "someHeaderValue" })}, - }; - } + [Fact] + public void should_not_match_mismatching_request_headers() + { + this.Given(_ => GivenPresetHeaderDictionary()) + .And(_ => AndGivenMismatchingRequestHeaders()) + .When(_ => WhenICreate()) + .Then(_ => ThenHasAnyOfIs(false)) + .And(_ => ThenHasAllOfIs(false)) + .BDDfy(); + } - private void AndGivenOneMatchingHeaderWithMismatchingValue() - { - _requestHeaders = new HeaderDictionary() { - { "testHeader1", new StringValues(new []{ "mismatchingValue" })}, - }; - } + [Fact] + public void should_not_match_matching_header_with_mismatching_value() + { + this.Given(_ => GivenPresetHeaderDictionary()) + .And(_ => AndGivenOneMatchingHeaderWithMismatchingValue()) + .When(_ => WhenICreate()) + .Then(_ => ThenHasAnyOfIs(false)) + .And(_ => ThenHasAllOfIs(false)) + .BDDfy(); + } - private void AndGivenOneMatchingHeaderWithMatchingValue() - { - _requestHeaders = new HeaderDictionary() { - { "testHeader1", new StringValues(new []{ "testHeader1Value1" })}, - }; - } + [Fact] + public void should_match_any_header_not_all() + { + this.Given(_ => GivenPresetHeaderDictionary()) + .And(_ => AndGivenOneMatchingHeaderWithMatchingValue()) + .When(_ => WhenICreate()) + .Then(_ => ThenHasAnyOfIs(true)) + .And(_ => ThenHasAllOfIs(false)) + .BDDfy(); + } - private void AndGivenTwoMatchingHeadersWithMatchingValues() - { - _requestHeaders = new HeaderDictionary() { - { "testHeader1", new StringValues(new []{ "testHeader1Value1", "bogusValue" })}, - { "testHeader2", new StringValues(new []{ "bogusValue", "testHeader2Value2" })}, - }; - } + [Fact] + public void should_match_any_and_all_headers() + { + this.Given(_ => GivenPresetHeaderDictionary()) + .And(_ => AndGivenTwoMatchingHeadersWithMatchingValues()) + .When(_ => WhenICreate()) + .Then(_ => ThenHasAnyOfIs(true)) + .And(_ => ThenHasAllOfIs(true)) + .BDDfy(); + } - private void WhenICreate() - { - _upstreamRoutingHeaders = new UpstreamRoutingHeaders(_headersDictionary); - } + private void GivenEmptyHeaderDictionary() + { + _headersDictionary = new Dictionary>(); + } - private void ThenAnyIs(bool expected) + private void GivenPresetHeaderDictionary() + { + _headersDictionary = new Dictionary>() { - _upstreamRoutingHeaders.Any().ShouldBe(expected); - } + { "testheader1", new HashSet() { "testheader1value1", "testheader1value2" } }, + { "testheader2", new HashSet() { "testheader1Value1", "testheader2value2" } }, + }; + } - private void ThenHasAnyOfIs(bool expected) - { - _upstreamRoutingHeaders.HasAnyOf(_requestHeaders).ShouldBe(expected); - } + private void AndGivenMismatchingRequestHeaders() + { + _requestHeaders = new HeaderDictionary() { + { "someHeader", new StringValues(new []{ "someHeaderValue" })}, + }; + } - private void ThenHasAllOfIs(bool expected) - { - _upstreamRoutingHeaders.HasAllOf(_requestHeaders).ShouldBe(expected); - } + private void AndGivenOneMatchingHeaderWithMismatchingValue() + { + _requestHeaders = new HeaderDictionary() { + { "testHeader1", new StringValues(new []{ "mismatchingValue" })}, + }; + } + + private void AndGivenOneMatchingHeaderWithMatchingValue() + { + _requestHeaders = new HeaderDictionary() { + { "testHeader1", new StringValues(new []{ "testHeader1Value1" })}, + }; + } + + private void AndGivenTwoMatchingHeadersWithMatchingValues() + { + _requestHeaders = new HeaderDictionary() { + { "testHeader1", new StringValues(new []{ "testHeader1Value1", "bogusValue" })}, + { "testHeader2", new StringValues(new []{ "bogusValue", "testHeader2Value2" })}, + }; + } + + private void WhenICreate() + { + _upstreamRoutingHeaders = new UpstreamRoutingHeaders(_headersDictionary); + } + + private void ThenAnyIs(bool expected) + { + _upstreamRoutingHeaders.Any().ShouldBe(expected); + } + + private void ThenHasAnyOfIs(bool expected) + { + _upstreamRoutingHeaders.HasAnyOf(_requestHeaders).ShouldBe(expected); + } + + private void ThenHasAllOfIs(bool expected) + { + _upstreamRoutingHeaders.HasAllOf(_requestHeaders).ShouldBe(expected); } } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index 1604605ef..ee9e9fe33 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -828,9 +828,9 @@ private void GivenTheUpstreamHttpMethodIs(string upstreamHttpMethod) private void GivenUpstreamHeaderRoutingOptions() { - var headers = new Dictionary>() + var headers = new Dictionary>() { - { "header", new HashSet() { "value" }}, + { "header", new[] { "value" }}, }; _upstreamHeaderRoutingOptions = new UpstreamHeaderRoutingOptions(headers, UpstreamHeaderRoutingTriggerMode.All); }