Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
36a1e28
routing based on headers (all specified headers must match)
jlukawska Jul 7, 2020
3630d50
routing based on headers for aggregated routes
jlukawska Jul 9, 2020
c392809
unit tests and small modifications
jlukawska Jul 10, 2020
1b74d53
find placeholders in header templates
jlukawska Jul 15, 2020
e121c8a
match upstream headers to header templates
jlukawska Jul 15, 2020
073c6fe
find placeholders name and values, fix regex for finding placeholders…
jlukawska Jul 16, 2020
800a025
fix unit tests
jlukawska Jul 23, 2020
a72a3d9
change header placeholder pattern
jlukawska Jul 24, 2020
c2829e7
unit tests
jlukawska Jul 28, 2020
e14ce8e
unit tests
jlukawska Jul 28, 2020
4ad836a
unit tests
jlukawska Jul 29, 2020
565e1c2
unit tests
jlukawska Aug 3, 2020
d797679
extend validation with checking upstreamheadertemplates, acceptance t…
jlukawska Aug 5, 2020
8c9cd40
update docs and minor changes
jlukawska Aug 13, 2020
cac4c3b
SA1649 File name should match first type name
raman-m Aug 2, 2023
d2b9cd7
Fix compilation errors by code review after resolving conflicts
raman-m Aug 2, 2023
30f5b3d
Fix warnings
raman-m Aug 2, 2023
2a32d1f
File-scoped namespaces
raman-m Aug 2, 2023
dec3145
File-scoped namespace
raman-m Aug 2, 2023
e13f19a
Target-typed 'new' expressions (C# 9).
raman-m Aug 2, 2023
39edcaa
IDE1006 Naming rule violation: These words must begin with upper case…
raman-m Aug 2, 2023
2dcb983
Target-typed 'new' expressions (C# 9).
raman-m Aug 2, 2023
d6c8659
Fix build errors
raman-m Apr 2, 2024
29d58b7
DownstreamRouteBuilder
raman-m Apr 6, 2024
8a64955
AggregatesCreator
raman-m Apr 6, 2024
864b63a
IUpstreamHeaderTemplatePatternCreator, RoutesCreator
raman-m Apr 6, 2024
79adba6
UpstreamHeaderTemplatePatternCreator
raman-m Apr 6, 2024
9a1d117
FileAggregateRoute
raman-m Apr 6, 2024
7b2dd43
FileAggregateRoute
raman-m Apr 6, 2024
0bf166e
FileRoute
raman-m Apr 6, 2024
a9f3f0e
Route, IRoute
raman-m Apr 6, 2024
790d590
FileConfigurationFluentValidator
raman-m Apr 6, 2024
38cd00c
OcelotBuilder
raman-m Apr 6, 2024
0667a93
DownstreamRouteCreator
raman-m Apr 6, 2024
079f9d2
DownstreamRouteFinder
raman-m Apr 6, 2024
32d8310
HeaderMatcher
raman-m Apr 6, 2024
36cf99f
DownstreamRouteFinderMiddleware
raman-m Apr 6, 2024
98fe814
UpstreamHeaderTemplate
raman-m Apr 6, 2024
ea55272
Routing folder
raman-m Apr 6, 2024
16f6f88
RoutingBasedOnHeadersTests
raman-m Apr 6, 2024
7f5bb67
Refactor acceptance tests
raman-m Apr 6, 2024
6e7ab00
AAA pattern in unit tests
raman-m Apr 12, 2024
6aeef96
CS8936: Feature 'collection expressions' is not available in C# 10.0.
raman-m Apr 12, 2024
c727b93
Code review by @RaynaldM
raman-m Apr 15, 2024
7e98824
Convert facts to one `Theory`
raman-m Apr 15, 2024
6a6a417
AAA pattern
raman-m Apr 15, 2024
75c9d85
Add traits
raman-m Apr 15, 2024
57e2fdb
Update routing.rst
raman-m Apr 15, 2024
dc5a671
Update docs
raman-m Apr 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions docs/features/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ Here is an example Route configuration. You don't need to set all of these thing
.. code-block:: json

{
"DownstreamPathTemplate": "/",
"UpstreamPathTemplate": "/",
"UpstreamHeaderTemplates": {}, // dictionary
"UpstreamHost": "",
"UpstreamHttpMethod": [ "Get" ],
"DownstreamPathTemplate": "/",
"DownstreamHttpMethod": "",
"DownstreamHttpVersion": "",
"AddHeadersToRequest": {},
Expand All @@ -37,7 +39,7 @@ Here is an example Route configuration. You don't need to set all of these thing
"ServiceName": "",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{ "Host": "localhost", "Port": 51876 }
{ "Host": "localhost", "Port": 12345 }
],
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 0,
Expand Down Expand Up @@ -70,7 +72,8 @@ Here is an example Route configuration. You don't need to set all of these thing
}
}

More information on how to use these options is below.
The actual Route schema for properties can be found in the C# `FileRoute <https://github.com/ThreeMammals/Ocelot/blob/main/src/Ocelot/Configuration/File/FileRoute.cs>`_ class.
If you're interested in learning more about how to utilize these options, read below!

Multiple Environments
---------------------
Expand Down
61 changes: 57 additions & 4 deletions docs/features/routing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,58 @@ The Route above will only be matched when the ``Host`` header value is ``somedom
If you do not set **UpstreamHost** on a Route then any ``Host`` header will match it.
This means that if you have two Routes that are the same, apart from the **UpstreamHost**, where one is null and the other set Ocelot will favour the one that has been set.

.. _routing-upstream-headers:

Upstream Headers [#f3]_
-----------------------

In addition to routing by ``UpstreamPathTemplate``, you can also define ``UpstreamHeaderTemplates``.
For a route to match, all headers specified in this dictionary object must be present in the request headers.

.. code-block:: json

{
// ...
"UpstreamPathTemplate": "/",
"UpstreamHttpMethod": [ "Get" ],
"UpstreamHeaderTemplates": { // dictionary
"country": "uk", // 1st header
"version": "v1" // 2nd header
}
}

In this scenario, the route will only match if a request includes both headers with the specified values.

Header placeholders
^^^^^^^^^^^^^^^^^^^

Let's explore a more intriguing scenario where placeholders can be effectively utilized within your ``UpstreamHeaderTemplates``.

Consider the following approach using the special placeholder format ``{header:placeholdername}``:

.. code-block:: json

{
"DownstreamPathTemplate": "/{versionnumber}/api", // with placeholder
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{ "Host": "10.0.10.1", "Port": 80 }
],
"UpstreamPathTemplate": "/api",
"UpstreamHttpMethod": [ "Get" ],
"UpstreamHeaderTemplates": {
"version": "{header:versionnumber}" // 'header:' prefix vs placeholder
}
}

In this scenario, the entire value of the request header "**version**" is inserted into the ``DownstreamPathTemplate``.
If necessary, a more intricate upstream header template can be specified, using placeholders such as ``version-{header:version}_country-{header:country}``.

**Note 1**: Placeholders are not required in ``DownstreamPathTemplate``.
This scenario can be utilized to mandate a specific header regardless of its value.

**Note 2**: Additionally, the ``UpstreamHeaderTemplates`` dictionary options are applicable for :doc:`../features/requestaggregation` as well.

Priority
--------

Expand Down Expand Up @@ -294,7 +346,7 @@ Here are two user scenarios.

.. _routing-security-options:

Security Options [#f3]_
Security Options [#f4]_
-----------------------

Ocelot allows you to manage multiple patterns for allowed/blocked IPs using the `IPAddressRange <https://github.com/jsakamoto/ipaddressrange>`_ package
Expand Down Expand Up @@ -326,7 +378,7 @@ The current patterns managed are the following:

.. _routing-dynamic:

Dynamic Routing [#f4]_
Dynamic Routing [#f5]_
----------------------

The idea is to enable dynamic routing when using a :doc:`../features/servicediscovery` provider so you don't have to provide the Route config.
Expand All @@ -336,5 +388,6 @@ See the :ref:`sd-dynamic-routing` docs if this sounds interesting to you.

.. [#f1] ":ref:`routing-empty-placeholders`" feature is available starting in version `23.0 <https://github.com/ThreeMammals/Ocelot/releases/tag/23.0.0>`_, see issue `748 <https://github.com/ThreeMammals/Ocelot/issues/748>`_ and the `23.0 <https://github.com/ThreeMammals/Ocelot/releases/tag/23.0.0>`__ release notes for details.
.. [#f2] ":ref:`routing-upstream-host`" feature was requested as part of `issue 216 <https://github.com/ThreeMammals/Ocelot/pull/216>`_.
.. [#f3] ":ref:`routing-security-options`" feature was requested as part of `issue 628 <https://github.com/ThreeMammals/Ocelot/issues/628>`_ (of `12.0.1 <https://github.com/ThreeMammals/Ocelot/releases/tag/12.0.1>`_ version), then redesigned and improved by `issue 1400 <https://github.com/ThreeMammals/Ocelot/issues/1400>`_, and published in version `20.0 <https://github.com/ThreeMammals/Ocelot/releases/tag/20.0.0>`_ docs.
.. [#f4] ":ref:`routing-dynamic`" feature was requested as part of `issue 340 <https://github.com/ThreeMammals/Ocelot/issues/340>`_. Complete reference: :ref:`sd-dynamic-routing`.
.. [#f3] ":ref:`routing-upstream-headers`" feature was proposed in `issue 360 <https://github.com/ThreeMammals/Ocelot/issues/360>`_, and released in version `24.0 <https://github.com/ThreeMammals/Ocelot/releases/tag/24.0.0>`_.
.. [#f4] ":ref:`routing-security-options`" feature was requested as part of `issue 628 <https://github.com/ThreeMammals/Ocelot/issues/628>`_ (of `12.0.1 <https://github.com/ThreeMammals/Ocelot/releases/tag/12.0.1>`_ version), then redesigned and improved by `issue 1400 <https://github.com/ThreeMammals/Ocelot/issues/1400>`_, and published in version `20.0 <https://github.com/ThreeMammals/Ocelot/releases/tag/20.0.0>`_ docs.
.. [#f5] ":ref:`routing-dynamic`" feature was requested as part of `issue 340 <https://github.com/ThreeMammals/Ocelot/issues/340>`_. Complete reference: :ref:`sd-dynamic-routing`.
22 changes: 16 additions & 6 deletions src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,14 @@ public class DownstreamRouteBuilder
private SecurityOptions _securityOptions;
private string _downstreamHttpMethod;
private Version _downstreamHttpVersion;
private Dictionary<string, UpstreamHeaderTemplate> _upstreamHeaders;

public DownstreamRouteBuilder()
{
_downstreamAddresses = new List<DownstreamHostAndPort>();
_delegatingHandlers = new List<string>();
_addHeadersToDownstream = new List<AddHeader>();
_addHeadersToUpstream = new List<AddHeader>();
_downstreamAddresses = new();
_delegatingHandlers = new();
_addHeadersToDownstream = new();
_addHeadersToUpstream = new();
}

public DownstreamRouteBuilder WithDownstreamAddresses(List<DownstreamHostAndPort> downstreamAddresses)
Expand Down Expand Up @@ -87,7 +88,9 @@ public DownstreamRouteBuilder WithUpstreamPathTemplate(UpstreamPathTemplate inpu

public DownstreamRouteBuilder WithUpstreamHttpMethod(List<string> input)
{
_upstreamHttpMethod = (input.Count == 0) ? new List<HttpMethod>() : input.Select(x => new HttpMethod(x.Trim())).ToList();
_upstreamHttpMethod = input.Count > 0
? input.Select(x => new HttpMethod(x.Trim())).ToList()
: new();
return this;
}

Expand Down Expand Up @@ -259,6 +262,12 @@ public DownstreamRouteBuilder WithDownstreamHttpVersion(Version downstreamHttpVe
return this;
}

public DownstreamRouteBuilder WithUpstreamHeaders(Dictionary<string, UpstreamHeaderTemplate> input)
{
_upstreamHeaders = input;
return this;
}

public DownstreamRoute Build()
{
return new DownstreamRoute(
Expand Down Expand Up @@ -295,6 +304,7 @@ public DownstreamRoute Build()
_dangerousAcceptAnyServerCertificateValidator,
_securityOptions,
_downstreamHttpMethod,
_downstreamHttpVersion);
_downstreamHttpVersion,
_upstreamHeaders);
}
}
12 changes: 10 additions & 2 deletions src/Ocelot/Configuration/Builder/RouteBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ public class RouteBuilder
private string _upstreamHost;
private List<DownstreamRoute> _downstreamRoutes;
private List<AggregateRouteConfig> _downstreamRoutesConfig;
private string _aggregator;
private string _aggregator;
private IDictionary<string, UpstreamHeaderTemplate> _upstreamHeaders;

public RouteBuilder()
{
Expand Down Expand Up @@ -58,6 +59,12 @@ public RouteBuilder WithAggregator(string aggregator)
{
_aggregator = aggregator;
return this;
}

public RouteBuilder WithUpstreamHeaders(IDictionary<string, UpstreamHeaderTemplate> upstreamHeaders)
{
_upstreamHeaders = upstreamHeaders;
return this;
}

public Route Build()
Expand All @@ -68,7 +75,8 @@ public Route Build()
_upstreamHttpMethod,
_upstreamTemplatePattern,
_upstreamHost,
_aggregator
_aggregator,
_upstreamHeaders
);
}
}
Expand Down
14 changes: 9 additions & 5 deletions src/Ocelot/Configuration/Creator/AggregatesCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ namespace Ocelot.Configuration.Creator
{
public class AggregatesCreator : IAggregatesCreator
{
private readonly IUpstreamTemplatePatternCreator _creator;
private readonly IUpstreamTemplatePatternCreator _creator;
private readonly IUpstreamHeaderTemplatePatternCreator _headerCreator;

public AggregatesCreator(IUpstreamTemplatePatternCreator creator)
public AggregatesCreator(IUpstreamTemplatePatternCreator creator, IUpstreamHeaderTemplatePatternCreator headerCreator)
{
_creator = creator;
_creator = creator;
_headerCreator = headerCreator;
}

public List<Route> Create(FileConfiguration fileConfiguration, List<Route> routes)
Expand All @@ -35,15 +37,17 @@ private Route SetUpAggregateRoute(IEnumerable<Route> routes, FileAggregateRoute
applicableRoutes.Add(downstreamRoute);
}

var upstreamTemplatePattern = _creator.Create(aggregateRoute);
var upstreamTemplatePattern = _creator.Create(aggregateRoute);
var upstreamHeaderTemplates = _headerCreator.Create(aggregateRoute);

var route = new RouteBuilder()
.WithUpstreamHttpMethod(aggregateRoute.UpstreamHttpMethod)
.WithUpstreamPathTemplate(upstreamTemplatePattern)
.WithDownstreamRoutes(applicableRoutes)
.WithAggregateRouteConfig(aggregateRoute.RouteKeysConfig)
.WithUpstreamHost(aggregateRoute.UpstreamHost)
.WithAggregator(aggregateRoute.Aggregator)
.WithAggregator(aggregateRoute.Aggregator)
.WithUpstreamHeaders(upstreamHeaderTemplates)
.Build();

return route;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Ocelot.Configuration.File;
using Ocelot.Values;

namespace Ocelot.Configuration.Creator;

/// <summary>
/// Ocelot feature: <see href="https://github.com/ThreeMammals/Ocelot/blob/develop/docs/features/routing.rst#upstream-headers">Routing based on request header</see>.
/// </summary>
public interface IUpstreamHeaderTemplatePatternCreator
{
/// <summary>
/// Creates upstream templates based on route headers.
/// </summary>
/// <param name="route">The route info.</param>
/// <returns>An <see cref="IDictionary{TKey, TValue}"/> object where TKey is <see langword="string"/>, TValue is <see cref="UpstreamHeaderTemplate"/>.</returns>
IDictionary<string, UpstreamHeaderTemplate> Create(IRoute route);
}
14 changes: 9 additions & 5 deletions src/Ocelot/Configuration/Creator/RoutesCreator.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Ocelot.Cache;
using Ocelot.Configuration.Builder;
using Ocelot.Configuration.File;

namespace Ocelot.Configuration.Creator
{
public class RoutesCreator : IRoutesCreator
Expand All @@ -10,6 +10,7 @@ public class RoutesCreator : IRoutesCreator
private readonly IClaimsToThingCreator _claimsToThingCreator;
private readonly IAuthenticationOptionsCreator _authOptionsCreator;
private readonly IUpstreamTemplatePatternCreator _upstreamTemplatePatternCreator;
private readonly IUpstreamHeaderTemplatePatternCreator _upstreamHeaderTemplatePatternCreator;
private readonly IRequestIdKeyCreator _requestIdKeyCreator;
private readonly IQoSOptionsCreator _qosOptionsCreator;
private readonly IRouteOptionsCreator _fileRouteOptionsCreator;
Expand Down Expand Up @@ -37,8 +38,8 @@ public RoutesCreator(
ILoadBalancerOptionsCreator loadBalancerOptionsCreator,
IRouteKeyCreator routeKeyCreator,
ISecurityOptionsCreator securityOptionsCreator,
IVersionCreator versionCreator
)
IVersionCreator versionCreator,
IUpstreamHeaderTemplatePatternCreator upstreamHeaderTemplatePatternCreator)
{
_routeKeyCreator = routeKeyCreator;
_loadBalancerOptionsCreator = loadBalancerOptionsCreator;
Expand All @@ -56,6 +57,7 @@ IVersionCreator versionCreator
_loadBalancerOptionsCreator = loadBalancerOptionsCreator;
_securityOptionsCreator = securityOptionsCreator;
_versionCreator = versionCreator;
_upstreamHeaderTemplatePatternCreator = upstreamHeaderTemplatePatternCreator;
}

public List<Route> Create(FileConfiguration fileConfiguration)
Expand Down Expand Up @@ -150,13 +152,15 @@ private DownstreamRoute SetUpDownstreamRoute(FileRoute fileRoute, FileGlobalConf

private Route SetUpRoute(FileRoute fileRoute, DownstreamRoute downstreamRoutes)
{
var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileRoute);
var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileRoute);
var upstreamHeaderTemplates = _upstreamHeaderTemplatePatternCreator.Create(fileRoute);

var route = new RouteBuilder()
.WithUpstreamHttpMethod(fileRoute.UpstreamHttpMethod)
.WithUpstreamPathTemplate(upstreamTemplatePattern)
.WithDownstreamRoute(downstreamRoutes)
.WithUpstreamHost(fileRoute.UpstreamHost)
.WithUpstreamHost(fileRoute.UpstreamHost)
.WithUpstreamHeaders(upstreamHeaderTemplates)
.Build();

return route;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using Ocelot.Configuration.File;
using Ocelot.Values;

namespace Ocelot.Configuration.Creator;

/// <summary>
/// Default creator of upstream templates based on route headers.
/// </summary>
/// <remarks>Ocelot feature: Routing based on request header.</remarks>
public partial class UpstreamHeaderTemplatePatternCreator : IUpstreamHeaderTemplatePatternCreator
{
Comment thread
raman-m marked this conversation as resolved.
private const string PlaceHolderPattern = @"(\{header:.*?\})";
#if NET7_0_OR_GREATER
[GeneratedRegex(PlaceHolderPattern, RegexOptions.IgnoreCase | RegexOptions.Singleline, "en-US")]
private static partial Regex RegExPlaceholders();
#else
private static readonly Regex RegExPlaceholdersVar = new(PlaceHolderPattern, RegexOptions.IgnoreCase | RegexOptions.Singleline, TimeSpan.FromMilliseconds(1000));
private static Regex RegExPlaceholders() => RegExPlaceholdersVar;
#endif

public IDictionary<string, UpstreamHeaderTemplate> Create(IRoute route)
{
var result = new Dictionary<string, UpstreamHeaderTemplate>();

foreach (var headerTemplate in route.UpstreamHeaderTemplates)
{
var headerTemplateValue = headerTemplate.Value;
var matches = RegExPlaceholders().Matches(headerTemplateValue);

if (matches.Count > 0)
Comment thread
raman-m marked this conversation as resolved.
{
var placeholders = matches.Select(m => m.Groups[1].Value).ToArray();
for (int i = 0; i < placeholders.Length; i++)
{
var indexOfPlaceholder = headerTemplateValue.IndexOf(placeholders[i]);
var placeholderName = placeholders[i][8..^1]; // remove "{header:" and "}"
headerTemplateValue = headerTemplateValue.Replace(placeholders[i], $"(?<{placeholderName}>.+)");
}
}

var template = route.RouteIsCaseSensitive
? $"^{headerTemplateValue}$"
: $"^(?i){headerTemplateValue}$"; // ignore case

result.Add(headerTemplate.Key, new(template, headerTemplate.Value));
}

return result;
}
}
9 changes: 6 additions & 3 deletions src/Ocelot/Configuration/DownstreamRoute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public DownstreamRoute(
bool dangerousAcceptAnyServerCertificateValidator,
SecurityOptions securityOptions,
string downstreamHttpMethod,
Version downstreamHttpVersion)
Version downstreamHttpVersion,
Dictionary<string, UpstreamHeaderTemplate> upstreamHeaders)
{
DangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator;
AddHeadersToDownstream = addHeadersToDownstream;
Expand Down Expand Up @@ -74,7 +75,8 @@ public DownstreamRoute(
AddHeadersToUpstream = addHeadersToUpstream;
SecurityOptions = securityOptions;
DownstreamHttpMethod = downstreamHttpMethod;
DownstreamHttpVersion = downstreamHttpVersion;
DownstreamHttpVersion = downstreamHttpVersion;
UpstreamHeaders = upstreamHeaders ?? new();
}

public string Key { get; }
Expand Down Expand Up @@ -110,6 +112,7 @@ public DownstreamRoute(
public bool DangerousAcceptAnyServerCertificateValidator { get; }
public SecurityOptions SecurityOptions { get; }
public string DownstreamHttpMethod { get; }
public Version DownstreamHttpVersion { get; }
public Version DownstreamHttpVersion { get; }
public Dictionary<string, UpstreamHeaderTemplate> UpstreamHeaders { get; }
}
}
Loading