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

Implement header based routing #448

Merged
merged 10 commits into from
Oct 6, 2020
Merged

Implement header based routing #448

merged 10 commits into from
Oct 6, 2020

Conversation

Tratcher
Copy link
Member

@Tratcher Tratcher commented Sep 29, 2020

Fixes #405

Design points:

  • The ability to specify one or more header and value(s) that must be present on a request in order to route.
  • Exposed from both code and config.
    • Should we allow you to specify a route using only a header? Today routes must have a path or host.
    • In config I've included both a Value and Values properties for convivence in the common single value scenario. Values takes priority if set. We may also want to do this with Host and Hosts, Header and Headers.
  • Headers are lower priority matches than paths, methods, and hosts. One unanticipated side effect of this I saw was if one route specifies methods and another specifies headers, the methods route will always win as more specific. The headers check only comes into play if the higher priority checks are equal.
  • Comparisons are case-insensitive by default to match the rest of routing, but you can opt-into case-sensitive checks.
    • Headers do not have consistent semantics like other parts of the request. Casing shouldn't matter in the average scenario, but there are some data types such as base64 encoded values where casing significantly changes the interpretation.
    • The case sensitivity option is implemented separately from match modes. When I included some of the match modes we may add in the future, and tried merging them with the case in/sensitive option it got very verbose and redundant. E.g. ExactHeaderCaseSensitive, ExactHeaderCaseInsensitive, ExactValueCaseSensitive, ExactValueCaseInsensitive, etc.. Even regexes have an optional, separate case sensitivity flag.
  • Only a limited set of header matching rules have been implemented, scoped to the customer requirements. See the docs for details.
    • Multi-header and multi-value headers are not supported.
    • Complex matching such as regex patterns are not supported.
    • The matching rules are specified separately from the values because there's no well-defined pattern syntax for header formats short of a full regex. The customer requirements so far only call for basic value matching.
  • Routing has some advanced performance features like INodeBuilderPolicy that we are not implementing in this first pass. They add significant complexity and it's not clear yet if they're needed for our scenarios.

Other oddities:

  • Bumped the 3.1 dependencies to 3.1.8 to pick up a fix in routing.

@Tratcher Tratcher added this to the 1.0.0-preview6 milestone Sep 29, 2020
@Tratcher Tratcher self-assigned this Sep 29, 2020
@Tratcher Tratcher marked this pull request as ready for review September 29, 2020 23:13
Copy link
Contributor

@alnikola alnikola left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall besides some comments on test coverage below.

@JamesNK
Copy link
Member

JamesNK commented Oct 1, 2020

Routing has some advanced performance features like INodeBuilderPolicy that we are not implementing in this first pass. They add significant complexity and it's not clear yet if they're needed for our scenarios.

The main benefit is performance. It moves logic from being executed per-request to at startup when the DFA is built.

Copy link
Contributor

@alnikola alnikola left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link

@javiercn javiercn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

@samsp-msft
Copy link
Contributor

  • Headers are lower priority matches than paths, methods, and hosts. One unanticipated side effect of this I saw was if one route specifies methods and another specifies headers, the methods route will always win as more specific. The headers check only comes into play if the higher priority checks are equal.

Is there a way to add a manual priority/weight to routes to bypass the default ordering. For example if I want all requests with a debug header to go to a specific destination, could I flag that rule in some way to push it to the top of the stack?

@javiercn
Copy link

javiercn commented Oct 2, 2020

Is there a way to add a manual priority/weight to routes to bypass the default ordering. For example if I want all requests with a debug header to go to a specific destination, could I flag that rule in some way to push it to the top of the stack?

If you are able to specify the route order explicitly that takes precedence over anything else. Routing supports that, I'm not sure if YARP exposes it, but it would be something good to expose.

@Tratcher
Copy link
Member Author

Tratcher commented Oct 2, 2020

Yes, YARP exposes the Order option on routes.

docs/docfx/articles/header-routing.md Outdated Show resolved Hide resolved

### IsCaseSensitive

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we demonstrate this in one of the examples?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know. It's more of an escape hatch for people, I'm not sure how much it will get used.

// TODO: Matches individual values from multi-value headers (split by coma, or semicolon for cookies).
// Also supports multiple headers of the same name.
// ExactValue,
// ValuePrefix,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we decide to do this, I like ContainsExactValue and ContainsValuePrefix names, but I'm not sure how common of a requirement matching individual values from multi-value headers will be.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're not doing these right now, we'll wait for feedback first. I left them here as examples of possible future values.

I don't know about Contains, it has mixed connotations from string.Contains and ICollection.Contains, we'd want the latter.

};
}

internal static bool Equals(RouteHeader header1, RouteHeader header2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use a internal static Equals instead of overriding object.Equals?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To simplify the calling code when either value might be null.

src/ReverseProxy/Service/Routing/HeaderMatcher.cs Outdated Show resolved Hide resolved

### Precedence

Route match precedence order is 1) path, 2) method, 3) host, 4) headers. That means a route that specifies methods and no headers will match before a route which specifies headers and no methods.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did we come up with this precedence order? I feel like host should be first or last overall even if it's technically just another header.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first three are already built into AspNetCore routing with that precedence. Our only choice is where to rank headers. Per some other comments I'll add a note here that this can be overridden per route by setting the Order property.

src/ReverseProxy/Configuration/Contract/RouteHeaderData.cs Outdated Show resolved Hide resolved
@Tratcher
Copy link
Member Author

Tratcher commented Oct 5, 2020

/azp run

@azure-pipelines
Copy link

No pipelines are associated with this pull request.

@Tratcher Tratcher closed this Oct 6, 2020
@Tratcher Tratcher reopened this Oct 6, 2020
@Tratcher Tratcher merged commit 7a6f66c into master Oct 6, 2020
@Tratcher Tratcher deleted the tratcher/headerroute branch October 6, 2020 18:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Proxy should enable routing based on header values
7 participants