Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upstream header routing #964

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
65 changes: 49 additions & 16 deletions docs/features/routing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ 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 ReRoute. In order to get
Ocelot's describes the routing of one request to another as a ReRoute. In order to get
anything working in Ocelot you need to set up a ReRoute in the configuration.

.. code-block:: json
Expand All @@ -32,18 +32,18 @@ To configure a ReRoute you need to add one to the ReRoutes json array.
"UpstreamHttpMethod": [ "Put", "Delete" ]
}

The DownstreamPathTemplate, DownstreamScheme and DownstreamHostAndPorts define the URL that a request will be forwarded to.
The DownstreamPathTemplate, DownstreamScheme and DownstreamHostAndPorts define the URL that a request will be forwarded to.

DownstreamHostAndPorts is a collection that defines the host and port of any downstream services that you wish to forward requests to.
DownstreamHostAndPorts is a collection that defines the host and port of any downstream services that you wish to forward requests to.
Usually this will just contain a single entry but sometimes you might want to load balance requests to your downstream services and Ocelot allows you add more than one entry and then select a load balancer.

The UpstreamPathTemplate is the URL that Ocelot will use to identify which DownstreamPathTemplate to use for a given request.
The UpstreamHttpMethod is used so Ocelot can distinguish between requests with different HTTP verbs to the same URL. You can set a specific list of HTTP Methods or set an empty list to allow any of them.
The UpstreamPathTemplate is the URL that Ocelot will use to identify which DownstreamPathTemplate to use for a given request.
The UpstreamHttpMethod is used so Ocelot can distinguish between requests with different HTTP verbs to the same URL. You can set a specific list of HTTP Methods or set an empty list to allow any of them.

In Ocelot you can add placeholders for variables to your Templates in the form of {something}.
The placeholder variable needs to be present in both the DownstreamPathTemplate and UpstreamPathTemplate properties. When it is Ocelot will attempt to substitute the value in the UpstreamPathTemplate placeholder into the DownstreamPathTemplate for each request Ocelot processes.

You can also do a catch all type of ReRoute e.g.
You can also do a catch all type of ReRoute e.g.

.. code-block:: json

Expand Down Expand Up @@ -72,7 +72,7 @@ In order to change this you can specify on a per ReRoute basis the following set
"ReRouteIsCaseSensitive": true

This means that when Ocelot tries to match the incoming upstream url with an upstream template the
evaluation will be case sensitive.
evaluation will be case sensitive.

Catch All
^^^^^^^^^
Expand All @@ -96,7 +96,7 @@ If you set up your config like below, all requests will be proxied straight thro
"UpstreamHttpMethod": [ "Get" ]
}

The catch all has a lower priority than any other ReRoute. If you also have the ReRoute below in your config then Ocelot would match it before the catch all.
The catch all has a lower priority than any other ReRoute. If you also have the ReRoute below in your config then Ocelot would match it before the catch all.

.. code-block:: json

Expand All @@ -113,7 +113,7 @@ The catch all has a lower priority than any other ReRoute. If you also have the
"UpstreamHttpMethod": [ "Get" ]
}

Upstream Host
Upstream Host
^^^^^^^^^^^^^

This feature allows you to have ReRoutes based on the upstream host. This works by looking at the host header the client has used and then using this as part of the information we use to identify a ReRoute.
Expand All @@ -138,7 +138,7 @@ In order to use this feature please add the following to your config.

The ReRoute above will only be matched when the host header value is somedomain.com.

If you do not set UpstreamHost on a ReRoute then any host header will match it. This means that if you have two ReRoutes 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.
If you do not set UpstreamHost on a ReRoute then any host header will match it. This means that if you have two ReRoutes 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.

This feature was requested as part of `Issue 216 <https://github.com/ThreeMammals/Ocelot/pull/216>`_ .

Expand Down Expand Up @@ -166,7 +166,7 @@ e.g. you could have
"Priority": 0
}

and
and

.. code-block:: json

Expand All @@ -181,9 +181,9 @@ matched /goods/{catchAll} (because this is the first ReRoute in the list!).
Dynamic Routing
^^^^^^^^^^^^^^^

This feature was requested in `issue 340 <https://github.com/ThreeMammals/Ocelot/issues/340>`_.
This feature was requested in `issue 340 <https://github.com/ThreeMammals/Ocelot/issues/340>`_.

The idea is to enable dynamic routing when using a service discovery provider so you don't have to provide the ReRoute config. See the docs :ref:`service-discovery` if
The idea is to enable dynamic routing when using a service discovery provider so you don't have to provide the ReRoute config. See the docs :ref:`service-discovery` if
this sounds interesting to you.

Query Strings
Expand Down Expand Up @@ -241,5 +241,38 @@ Ocelot will also allow you to put query string parameters in the UpstreamPathTem
}
}

In this example Ocelot will only match requests that have a matching url path and the query string starts with unitId=something. You can have other queries after this
but you must start with the matching parameter. Also Ocelot will swap the {unitId} parameter from the query string and use it in the downstream request path.
In this example Ocelot will only match requests that have a matching url path and the query string starts with unitId=something. You can have other queries after this but you must start with the matching parameter. Also Ocelot will swap the {unitId} parameter from the query string and use it in the downstream request path.

Upstream header-based routing
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This feature was requested in `issue 360 <https://github.com/ThreeMammals/Ocelot/issues/360>`_ and `issue 624 <https://github.com/ThreeMammals/Ocelot/issues/624>`_.

Ocelot allows you to define a ReRoute with upstream headers, each of which may define a set of accepted values. If a ReRoute has a set of upstream headers defined in it, it will no longer match a request's upstream path based solely on upstream path template. The request must also contain one or more headers required by the ReRoute for a match.

A sample configuration might look like the following:

.. code-block:: json

{
"ReRoutes": [
{
"UpstreamHeaderRoutingOptions": {
"Headers": {
"X-API-Version": [ "1", "2" ],
"X-Tennant-Id": [ "tennantId" ]
},
"CombinationMode": "all"
}
}
]
}

The ``UpstreamHeaderRoutingOptions`` block defines two attributes -- the ``Headers`` block and the ``CombinationMode`` attribute. The ``Headers`` attribute defines required header names as keys and lists of acceptable header values as values. During route matching, both header names and values are matched in *case insensitive* manner. Please note that if a header has more than one acceptable value configured, presence of any of those values in a request is sufficient for a header to be a match.

The second attribute, ``CombinationMode``, defines how the route finder will determine whether a particular header configuration in a request matches a ReRoute's header configuration. The attribute accepts two values:

* ``"Any"`` causes the route finder to match a ReRoute if any value of any configured header is present in a request
* ``"All"`` causes the route finder to match a ReRoute only if any value of *all* configured headers is present in a request

The value for this attribute is case-insensitive and, if not specified, ``"Any"`` is used as the default.
29 changes: 18 additions & 11 deletions src/Ocelot/Configuration/Builder/ReRouteBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
namespace Ocelot.Configuration.Builder
{
using Ocelot.Configuration.File;
using Ocelot.Configuration.File;
using Ocelot.Values;
using System.Collections.Generic;
using System.Linq;
using System.Linq;
using System.Net.Http;

public class ReRouteBuilder
{
private UpstreamPathTemplate _upstreamTemplatePattern;
Expand All @@ -14,6 +14,7 @@ public class ReRouteBuilder
private List<DownstreamReRoute> _downstreamReRoutes;
private List<AggregateReRouteConfig> _downstreamReRoutesConfig;
private string _aggregator;
private UpstreamHeaderRoutingOptions _upstreamHeaderRoutingOptions;

public ReRouteBuilder()
{
Expand Down Expand Up @@ -55,24 +56,30 @@ public ReRouteBuilder WithAggregateReRouteConfig(List<AggregateReRouteConfig> ag
{
_downstreamReRoutesConfig = aggregateReRouteConfigs;
return this;
}

}
public ReRouteBuilder WithAggregator(string aggregator)
{
_aggregator = aggregator;
return this;
}

public ReRouteBuilder WithUpstreamHeaderRoutingOptions(UpstreamHeaderRoutingOptions routingOptions)
{
_upstreamHeaderRoutingOptions = routingOptions;
return this;
}

public ReRoute Build()
{
return new ReRoute(
_downstreamReRoutes,
_downstreamReRoutes,
_downstreamReRoutesConfig,
_upstreamHttpMethod,
_upstreamTemplatePattern,
_upstreamHttpMethod,
_upstreamTemplatePattern,
_upstreamHost,
_aggregator
);
_aggregator,
_upstreamHeaderRoutingOptions);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Ocelot.Configuration.File;

namespace Ocelot.Configuration.Creator
{
public interface IUpstreamHeaderRoutingOptionsCreator
{
UpstreamHeaderRoutingOptions Create(FileUpstreamHeaderRoutingOptions options);
}
}
8 changes: 7 additions & 1 deletion src/Ocelot/Configuration/Creator/ReRoutesCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class ReRoutesCreator : IReRoutesCreator
private readonly IDownstreamAddressesCreator _downstreamAddressesCreator;
private readonly IReRouteKeyCreator _reRouteKeyCreator;
private readonly ISecurityOptionsCreator _securityOptionsCreator;
private readonly IUpstreamHeaderRoutingOptionsCreator _upstreamHeaderRoutingOptionsCreator;

public ReRoutesCreator(
IClaimsToThingCreator claimsToThingCreator,
Expand All @@ -37,7 +38,8 @@ public ReRoutesCreator(
IDownstreamAddressesCreator downstreamAddressesCreator,
ILoadBalancerOptionsCreator loadBalancerOptionsCreator,
IReRouteKeyCreator reRouteKeyCreator,
ISecurityOptionsCreator securityOptionsCreator
ISecurityOptionsCreator securityOptionsCreator,
IUpstreamHeaderRoutingOptionsCreator upstreamHeaderRoutingOptionsCreator
)
{
_reRouteKeyCreator = reRouteKeyCreator;
Expand All @@ -55,6 +57,7 @@ ISecurityOptionsCreator securityOptionsCreator
_httpHandlerOptionsCreator = httpHandlerOptionsCreator;
_loadBalancerOptionsCreator = loadBalancerOptionsCreator;
_securityOptionsCreator = securityOptionsCreator;
_upstreamHeaderRoutingOptionsCreator = upstreamHeaderRoutingOptionsCreator;
}

public List<ReRoute> Create(FileConfiguration fileConfiguration)
Expand Down Expand Up @@ -144,11 +147,14 @@ private ReRoute SetUpReRoute(FileReRoute fileReRoute, DownstreamReRoute downstre
{
var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute);

var upstreamHeaderRoutingOptions = _upstreamHeaderRoutingOptionsCreator.Create(fileReRoute.UpstreamHeaderRoutingOptions);

var reRoute = new ReRouteBuilder()
.WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod)
.WithUpstreamPathTemplate(upstreamTemplatePattern)
.WithDownstreamReRoute(downstreamReRoutes)
.WithUpstreamHost(fileReRoute.UpstreamHost)
.WithUpstreamHeaderRoutingOptions(upstreamHeaderRoutingOptions)
.Build();

return reRoute;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Linq;
using System.Collections.Generic;
using Ocelot.Configuration.File;

namespace Ocelot.Configuration.Creator
{
public class UpstreamHeaderRoutingOptionsCreator : IUpstreamHeaderRoutingOptionsCreator
{
public UpstreamHeaderRoutingOptions Create(FileUpstreamHeaderRoutingOptions options)
{
UpstreamHeaderRoutingCombinationMode mode = UpstreamHeaderRoutingCombinationMode.Any;
if (options.CombinationMode.Length > 0)
{
mode = (UpstreamHeaderRoutingCombinationMode)
Enum.Parse(typeof(UpstreamHeaderRoutingCombinationMode), options.CombinationMode, true);
}

Dictionary<string, HashSet<string>> headers = options.Headers.ToDictionary(
kv => kv.Key.ToLowerInvariant(),
kv => new HashSet<string>(kv.Value.Select(v => v.ToLowerInvariant())));

return new UpstreamHeaderRoutingOptions(headers, mode);
}
}
}
2 changes: 2 additions & 0 deletions src/Ocelot/Configuration/File/FileReRoute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public FileReRoute()
DelegatingHandlers = new List<string>();
LoadBalancerOptions = new FileLoadBalancerOptions();
SecurityOptions = new FileSecurityOptions();
UpstreamHeaderRoutingOptions = new FileUpstreamHeaderRoutingOptions();
Priority = 1;
}

Expand Down Expand Up @@ -53,5 +54,6 @@ public FileReRoute()
public int Timeout { get; set; }
public bool DangerousAcceptAnyServerCertificateValidator { get; set; }
public FileSecurityOptions SecurityOptions { get; set; }
public FileUpstreamHeaderRoutingOptions UpstreamHeaderRoutingOptions { get; set; }
}
}
17 changes: 17 additions & 0 deletions src/Ocelot/Configuration/File/FileUpstreamHeaderRoutingOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Collections.Generic;

namespace Ocelot.Configuration.File
{
public class FileUpstreamHeaderRoutingOptions
{
public FileUpstreamHeaderRoutingOptions()
{
Headers = new Dictionary<string, List<string>>();
CombinationMode = "";
}

public Dictionary<string, List<string>> Headers { get; set; }

public string CombinationMode { get; set; }
}
}
11 changes: 7 additions & 4 deletions src/Ocelot/Configuration/ReRoute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,19 @@ public class ReRoute
{
public ReRoute(List<DownstreamReRoute> downstreamReRoute,
List<AggregateReRouteConfig> downstreamReRouteConfig,
List<HttpMethod> upstreamHttpMethod,
UpstreamPathTemplate upstreamTemplatePattern,
List<HttpMethod> upstreamHttpMethod,
UpstreamPathTemplate upstreamTemplatePattern,
string upstreamHost,
string aggregator)
string aggregator,
UpstreamHeaderRoutingOptions upstreamHeaderRoutingOptions)
{
UpstreamHost = upstreamHost;
DownstreamReRoute = downstreamReRoute;
DownstreamReRouteConfig = downstreamReRouteConfig;
UpstreamHttpMethod = upstreamHttpMethod;
UpstreamTemplatePattern = upstreamTemplatePattern;
Aggregator = aggregator;
UpstreamHeaderRoutingOptions = upstreamHeaderRoutingOptions;
}

public UpstreamPathTemplate UpstreamTemplatePattern { get; private set; }
Expand All @@ -28,5 +30,6 @@ public ReRoute(List<DownstreamReRoute> downstreamReRoute,
public List<DownstreamReRoute> DownstreamReRoute { get; private set; }
public List<AggregateReRouteConfig> DownstreamReRouteConfig { get; private set; }
public string Aggregator { get; private set; }
public UpstreamHeaderRoutingOptions UpstreamHeaderRoutingOptions { get; private set; }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Ocelot.Configuration
{
public enum UpstreamHeaderRoutingCombinationMode
{
Any = 0,
All = 1,
}
}
19 changes: 19 additions & 0 deletions src/Ocelot/Configuration/UpstreamHeaderRoutingOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Collections.Generic;

namespace Ocelot.Configuration
{
public class UpstreamHeaderRoutingOptions
{
public UpstreamHeaderRoutingOptions(Dictionary<string, HashSet<string>> headers, UpstreamHeaderRoutingCombinationMode mode)
{
Headers = new UpstreamRoutingHeaders(headers);
Mode = mode;
}

public bool Enabled() => !Headers.Empty();

public UpstreamRoutingHeaders Headers { get; private set; }

public UpstreamHeaderRoutingCombinationMode Mode { get; private set; }
}
}
Loading