Skip to content

Commit

Permalink
Add header based routing
Browse files Browse the repository at this point in the history
  • Loading branch information
Tratcher committed Oct 6, 2020
1 parent 6f0e409 commit 4ad649e
Show file tree
Hide file tree
Showing 30 changed files with 1,296 additions and 307 deletions.
2 changes: 1 addition & 1 deletion docs/docfx/articles/configfiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ The routes section is an ordered list of route matches and their associated conf
- ClusterId - Refers to the name of an entry in the clusters section.
- Match containing either a Hosts array or a Path pattern string.

[Authorization](authn-authz.md), [CORS](cors.md), and other route based policies can be configured on each route entry. For additional fields see [ProxyRoute](xref:Microsoft.ReverseProxy.Configuration.Contract.ProxyRouteData).
[Headers](header-routing.md), [Authorization](authn-authz.md), [CORS](cors.md), and other route based policies can be configured on each route entry. For additional fields see [ProxyRoute](xref:Microsoft.ReverseProxy.Configuration.Contract.ProxyRouteData).

The proxy will apply the given matching criteria and policies, and then pass off the request to the specified cluster.

Expand Down
2 changes: 1 addition & 1 deletion docs/docfx/articles/configproviders.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ The routes section is an ordered list of route matches and their associated conf
- ClusterId - Refers to the name of an entry in the clusters section.
- Match containing either a Hosts array or a Path pattern string.

[Authorization](authn-authz.md), [CORS](cors.md), and other route based policies can be configured on each route entry. For additional fields see [ProxyRoute](xref:Microsoft.ReverseProxy.Abstractions.ProxyRoute).
[Headers](header-routing.md), [Authorization](authn-authz.md), [CORS](cors.md), and other route based policies can be configured on each route entry. For additional fields see [ProxyRoute](xref:Microsoft.ReverseProxy.Abstractions.ProxyRoute).

The proxy will apply the given matching criteria and policies, and then pass off the request to the specified cluster.

Expand Down
283 changes: 283 additions & 0 deletions docs/docfx/articles/header-routing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
---
uid: header-routing
title: Header Routing
---

# Header Based Routing

Proxy routes specified in [config](configfiles.md) or via [code](configproviders.md) must include at least a path or host to match against. In addition to these, a route can also specify one or more headers that must be present on the request.

### Precedence

The default route match precedence order is 1) path, 2) method, 3) host, 4) headers. That means a route which specifies methods and no headers will match before a route which specifies headers and no methods. This can be overridden by setting the `Order` property on a route.

## Configuration

Headers are specified in the `Match` section of a proxy route.

If multiple headers rules are specified on a route then all must match for a route to be taken. OR logic must be implemented either within a header rule or as separate routes.

Configuration:
```JSON
"Routes": [
{
"RouteId": "route1",
"ClusterId": "cluster1",
"Match": {
"Path": "{**catch-all}",
"Headers": [
{
"Name": "header1",
"Values": [ "value1" ],
"Mode": "ExactHeader"
}
]
}
},
{
"RouteId": "route2",
"ClusterId": "cluster1",
"Match": {
"Path": "{**catch-all}",
"Headers": [
{
"Name": "header2",
"Values": [ "1prefix", "2prefix" ],
"Mode": "HeaderPrefix"
}
]
}
},
{
"RouteId": "route3",
"ClusterId": "cluster1",
"Match": {
"Path": "{**catch-all}",
"Headers": [
{
"Name": "header3",
"Mode": "Exists"
}
]
}
},
{
"RouteId": "route4",
"ClusterId": "cluster1",
"Match": {
"Path": "{**catch-all}",
"Headers": [
{
"Name": "header4",
"Values": [ "value1", "value2" ],
"Mode": "ExactHeader"
},
{
"Name": "header5",
"Mode": "Exists"
}
]
}
}
]
```

Code:
```C#
var routes = new[]
{
new ProxyRoute()
{
RouteId = "route1",
ClusterId = "cluster1",
Match =
{
Path = "{**catch-all}",
Headers = new[]
{
new RouteHeader()
{
Name = "Header1",
Values = new[] { "value1" },
Mode = HeaderMatchMode.ExactHeader
}
}
}
},
new ProxyRoute()
{
RouteId = "route2",
ClusterId = "cluster1",
Match =
{
Path = "{**catch-all}",
Headers = new[]
{
new RouteHeader()
{
Name = "Header2",
Values = new[] { "1prefix", "2prefix" },
Mode = HeaderMatchMode.HeaderPrefix
}
}
}
},
new ProxyRoute()
{
RouteId = "route3",
ClusterId = "cluster1",
Match =
{
Path = "{**catch-all}",
Headers = new[]
{
new RouteHeader()
{
Name = "Header3",
Mode = HeaderMatchMode.Exists
}
}
}
},
new ProxyRoute()
{
RouteId = "route4",
ClusterId = "cluster1",
Match =
{
Path = "{**catch-all}",
Headers = new[]
{
new RouteHeader()
{
Name = "Header4",
Values = new[] { "value1", "value2" },
Mode = HeaderMatchMode.ExactHeader
},
new RouteHeader()
{
Name = "Header5",
Mode = HeaderMatchMode.Exists
}
}
}
}
};
```

## Contract

[RouteHeaderData](xref:Microsoft.ReverseProxy.Configuration.Contract.RouteHeaderData) defines the configuration contract, where [RouteHeader](xref:Microsoft.ReverseProxy.Abstractions.RouteHeader) defines the code contract. The only difference in these contracts is that RouteHeaderData defines an extra `Value` property for convenience, `Values` is preferred if specified.

### Name

The header name to check for on the request. A non-empty value is required. This field is case-insensitive per the HTTP RFCs.

### Values

A list of possible values to search for. The header must match at least one of these values according to the specified `Mode`. At least one value is required unless `Mode` is set to `Exists`.

### Mode

[HeaderMatchMode](xref:Microsoft.ReverseProxy.Abstractions.HeaderMatchMode) specifies how to match the value(s) against the request header. The default is `ExactHeader`.
- ExactHeader - The header must match in its entirety, subject to the value of `IsCaseSensitive`. Only single headers are supported. If there are multiple headers with the same name then the match fails.
- HeaderPrefix - The header must match by prefix, subject to the value of `IsCaseSensitive`. Only single headers are supported. If there are multiple headers with the same name then the match fails.
- Exists - The header must exist and contain any non-empty value.

### IsCaseSensitive

Indicates if the value match should be performed as case sensitive or insensitive. The default is `false`, insensitive.

## Examples

These examples use the configuration specified above.

### Scenario 1 - Exact Header Match

A request with the following header will match against route1.
```
Header1: Value1
```

A header with multiple values is not currently supported and will _not_ match.
```
Header1: Value1, Value2
```

Multiple headers with the same name are not currently supported and will _not_ match.
```
Header1: Value1
Header1: Value2
```

### Scenario 2 - Multiple Values

Route2 defined multiple values to search for in a header ("1prefix", "2prefix"), any of the values are acceptable. It also specified the `Mode` as `HeaderPrefix`, so any header that starts with those values is acceptable.

Any of the following headers will match route2.
```
Header2: 1prefix
```
```
Header2: 2prefix
```
```
Header2: 1prefix-extra
```
```
Header2: 2prefix-extra
```

A header with multiple values is not currently supported and will _not_ match.
```
Header2: 1prefix, 2prefix
```

Multiple headers with the same name are not currently supported and will _not_ match.
```
Header2: 1prefix
Header2: 2prefix
```

### Scenario 3 - Exists

Route3 only requires that the header "Header3" exists with any non-empty value

The following is an example that will match route3.
```
Header3: value
```

An empty header will _not_ match.
```
Header3:
```

This mode does support headers with multiple values and multiple headers with the same name since it does not look at the header contents. The following will match.
```
Header3: value1, value2
```
```
Header3: value1
Header3: value2
```

### Scenario 4 - Multiple Headers

Route4 requires both `header4` and `header5`, each matching according to their specified `Mode`. The following headers will match route4:
```
Header4: value1
Header5: AnyValue
```
```
Header4: value2
Header5: AnyValue
```

These will _not_ match route4 because they are missing one of the required headers:
```
Header4: value2
```
```
Header5: AnyValue
```
2 changes: 2 additions & 0 deletions docs/docfx/articles/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
href: configproviders.md
- name: Proxy HTTP client configuration
href: proxyhttpclientconfig.md
- name: Header Routing
href: header-routing.md
- name: Authentication and Authorization
href: authn-authz.md
- name: Cross-Origin Requests (CORS)
Expand Down
8 changes: 4 additions & 4 deletions global.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@
"runtimes": {
"dotnet/x64": [
"$(MicrosoftNETCoreAppPackageVersion)",
"3.1.0"
"3.1.8"
],
"dotnet/x86": [
"$(MicrosoftNETCoreAppPackageVersion)",
"3.1.0"
"3.1.8"
],
"aspnetcore/x64": [
"3.1.0"
"3.1.8"
],
"aspnetcore/x86": [
"3.1.0"
"3.1.8"
]
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.ReverseProxy.Service.Routing;

namespace Microsoft.ReverseProxy.Abstractions
{
/// <summary>
Expand All @@ -9,7 +11,7 @@ namespace Microsoft.ReverseProxy.Abstractions
public enum HeaderMatchMode
{
/// <summary>
/// The header must match in its entirety, subject to the value of <see cref="IHeaderMetadata.CaseSensitive"/>.
/// The header must match in its entirety, subject to the value of <see cref="IHeaderMetadata.IsCaseSensitive"/>.
/// Only single headers are supported. If there are multiple headers with the same name then the match fails.
/// </summary>
ExactHeader,
Expand All @@ -20,7 +22,7 @@ public enum HeaderMatchMode
// ValuePrefix,

/// <summary>
/// The header must match by prefix, subject to the value of <see cref="IHeaderMetadata.CaseSensitive"/>.
/// The header must match by prefix, subject to the value of <see cref="IHeaderMetadata.IsCaseSensitive"/>.
/// Only single headers are supported. If there are multiple headers with the same name then the match fails.
/// </summary>
HeaderPrefix,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,7 @@ private static bool HeadersEqual(IReadOnlyList<RouteHeader> headers1, IReadOnlyL
return true;
}

if ((headers1?.Count ?? 0) == 0 && (headers2?.Count ?? 0) == 0)
{
return true;
}

if (headers1 != null && headers2 == null || headers1 == null && headers2 != null)
if (headers1 == null || headers2 == null)
{
return false;
}
Expand Down
Loading

0 comments on commit 4ad649e

Please sign in to comment.