Skip to content

Commit

Permalink
Integration of additional PR feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
mrclayman committed Aug 19, 2023
1 parent 4bc297a commit 6d29040
Show file tree
Hide file tree
Showing 12 changed files with 308 additions and 312 deletions.
34 changes: 31 additions & 3 deletions docs/features/routing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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"
}
}
]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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<UpstreamHeaderRoutingTriggerMode>(options.TriggerOn, true);
}
mode = Enum.Parse<UpstreamHeaderRoutingTriggerMode>(options.TriggerOn, true);
}

Dictionary<string, HashSet<string>> headers = options.Headers.ToDictionary(
kv => kv.Key.ToLowerInvariant(),
kv => new HashSet<string>(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);
}
}
17 changes: 5 additions & 12 deletions src/Ocelot/Configuration/File/FileUpstreamHeaderRoutingOptions.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
using System.Collections.Generic;

namespace Ocelot.Configuration.File
{
public class FileUpstreamHeaderRoutingOptions
{
public FileUpstreamHeaderRoutingOptions()
{
Headers = new Dictionary<string, IList<string>>();
TriggerOn = string.Empty;
}
namespace Ocelot.Configuration.File;

public IDictionary<string, IList<string>> Headers { get; set; }
public class FileUpstreamHeaderRoutingOptions
{
public IDictionary<string, ICollection<string>> Headers { get; set; } = new Dictionary<string, ICollection<string>>();

public string TriggerOn { get; set; }
}
public string TriggerOn { get; set; } = string.Empty;
}
21 changes: 10 additions & 11 deletions src/Ocelot/Configuration/UpstreamHeaderRoutingOptions.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
using System.Collections.Generic;

namespace Ocelot.Configuration
namespace Ocelot.Configuration;

public class UpstreamHeaderRoutingOptions
{
public class UpstreamHeaderRoutingOptions
public UpstreamHeaderRoutingOptions(IReadOnlyDictionary<string, ICollection<string>> headers, UpstreamHeaderRoutingTriggerMode mode)
{
public UpstreamHeaderRoutingOptions(IReadOnlyDictionary<string, HashSet<string>> 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; }
}
11 changes: 5 additions & 6 deletions src/Ocelot/Configuration/UpstreamHeaderRoutingTriggerMode.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
namespace Ocelot.Configuration
namespace Ocelot.Configuration;

public enum UpstreamHeaderRoutingTriggerMode : byte
{
public enum UpstreamHeaderRoutingTriggerMode
{
Any = 0,
All = 1,
}
Any,
All,
}
87 changes: 41 additions & 46 deletions src/Ocelot/Configuration/UpstreamRoutingHeaders.cs
Original file line number Diff line number Diff line change
@@ -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<string, HashSet<string>> Headers { get; }
public IReadOnlyDictionary<string, ICollection<string>> Headers { get; }

public UpstreamRoutingHeaders(IReadOnlyDictionary<string, HashSet<string>> headers)
{
Headers = headers;
}
public UpstreamRoutingHeaders(IReadOnlyDictionary<string, ICollection<string>> 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<string, HashSet<string>> 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<string> 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<string, HashSet<string>> h in Headers)
if (!normalizedHeaders.TryGetValue(h.Key, out var values))
{
if (!lowerCaseHeaders.TryGetValue(h.Key, out var values))
{
return false;
}

HashSet<string> 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<string, StringValues> 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<string, StringValues> kv in headers)
{
var key = kv.Key.ToUpperInvariant();
upperCaseHeaders.Add(key, kv.Value);
}

return upperCaseHeaders;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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<string, ICollection<string>> first,
IDictionary<string, ICollection<string>> 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<string> firstKeySet = first.UpstreamHeaderRoutingOptions.Headers.Keys
.Select(k => k.ToLowerInvariant())
.ToHashSet();
ISet<string> 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<string, IList<string>> secondHeaders = second.UpstreamHeaderRoutingOptions.Headers;
if (!secondHeaders.TryGetValue(key, out IList<string> secondHeaderValues))
if (!second.TryGetValue(key, out var secondValues))
{
continue;
}

ISet<string> firstHeaderValuesLowerCase = values
.Select(v => v.ToLowerInvariant())
.ToHashSet();
ISet<string> secondHeaderValuesLowerCase = secondHeaderValues
.Select(v => v.ToLowerInvariant())
.ToHashSet();

if (firstHeaderValuesLowerCase.Overlaps(secondHeaderValuesLowerCase))
if (firstValues.Intersect(secondValues, StringComparer.OrdinalIgnoreCase).Any())
{
return true;
}
Expand Down
Loading

0 comments on commit 6d29040

Please sign in to comment.