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

Proxy should enable routing based on header values #405

Closed
Tratcher opened this issue Sep 8, 2020 · 17 comments · Fixed by #448
Closed

Proxy should enable routing based on header values #405

Tratcher opened this issue Sep 8, 2020 · 17 comments · Fixed by #448
Assignees
Labels
Partner Blocking: Island Gateway Priority:0 Used for divisional .NET planning Type: Enhancement New feature or request User Story Used for divisional .NET planning

Comments

@Tratcher
Copy link
Member

Tratcher commented Sep 8, 2020

Multiple partners have asked for header based routing. I'll ask them to give examples below. @davidni has an implementation in Island Gateway and had been working on this as an AspNetCore 5 feature but it wasn't done in time (dotnet/aspnetcore#23594). The plan is to port Island Gateway's work to YARP so it works on 3.1 and 5.0 and then contribute it back to AspNetCore in 6.0.

@Tratcher
Copy link
Member Author

Tratcher commented Sep 8, 2020

@davidni, can you give some examples of the types of header routing you need to support?

@karelz karelz added this to the 1.0.0 milestone Sep 8, 2020
@johnazariah
Copy link
Member

johnazariah commented Sep 8, 2020

We at Azure Quantum are also blocked by the unavailability of this feature.

We are expecting to use this feature to enable a dynamic fleet of short-lived service instances in a dev-test situation.

We envisage passing in a header - say x-ms-stagingenv with values of staging1 and staging2 for two staging environments, and our CI-CD pipeline will stand up two environments with corresponding names - say staging1.rp.azurewebsites.net and staging2.rp.azurewebsites.net.

We want to be able to dynamically construct the clusters and routes to be able to allow the routing of a fixed URL to either staging1 or staging2 (or indeed staging17) based on the value of x-ms-stagingenv.

We will presumably want to be able to deregister environments after they have been decommissioned, so we will likely want to use some form of database to look up registered environments; or, if there is a way to dynamically implement the routing rule itself, maybe dispense with any registration and directly route to a constructed URL.

@Tratcher
Copy link
Member Author

Tratcher commented Sep 8, 2020

@johnazariah while that could be done with routing, it actually sounds a lot closer to A/B testing as described in #126.

@johnazariah
Copy link
Member

johnazariah commented Sep 8, 2020

Interesting...

So is there a way to attach affinity from a request to a specific route? And how can we specify targets without headers?

@Tratcher
Copy link
Member Author

Tratcher commented Sep 8, 2020

Let's move the discussion over to 126.

@samsp-msft
Copy link
Contributor

If this is for individual dev test scenarios, I don't think this A/B testing like #126.
What I imagine is this is actually more like session affinity. If a developer spins up a new instance of a service, and wants to route their requests to that service, then they add a header or cookie to the request to tell it to go to that instance. They can then debug that instance, step through code etc, but still in the context of the other services. As it may be being debugged, that instance is not going to want health checks, or load balancing.
I'm thinking it should go in middleware like session affinity, and once it sees the relevant header or cookie, it changes the host collection to be the specific destination for that request. This would enable it to go to a host that is not normally part of the route, and also to be torn down without needing to redo the destination table.

@karelz karelz modified the milestones: 1.0.0, 1.0.0-preview6 Sep 17, 2020
@karelz
Copy link
Member

karelz commented Sep 17, 2020

Triage: Needs design - match on full value with optional case sensitivity.

@jmezach
Copy link
Contributor

jmezach commented Sep 19, 2020

I think we'll want to have this as well. Could we perhaps help out with this?

@Tratcher
Copy link
Member Author

@jmezach can you start by sharing your requirements and scenarios?

We have a few implementations of this already, hopefully I just need to port them over and adapt them to YARP.

@jmezach
Copy link
Contributor

jmezach commented Sep 19, 2020

Well, one scenario where I think this will be helpful is for our test environments. Currently we're trying to host multiple test environments on the same servers. Since we're not in containerland yet we need to do pre- and postfixes to keep things from interfering with each other. That basically means our test environments currently are very different from production, which leads to all kinds of problems.

With YARP we feel this could be simplified if we have header based routing. We could let the client set a header indicating which environment they want to target and YARP would route traffic to that environment. But rather than having to do this for all our services, we could just do it for a single one. Obviously that single service would need to be deployed with a prefix or postfix, but it wouldn't require a whole separately configured client for example.

Does that make sense?

@Tratcher
Copy link
Member Author

Understood. Can you also describe or give examples of the headers themselves? Certainly you need to be able to specify the header name and value, but what are the matching rules?

  • is the header value match case in/sensitive?
  • how big are the values? That will affect matching performance.
  • do you need support for wildcard or pattern matches? E.g. staging-*.
  • do you need to be able to match one value from a multi-value header? E.g. env: staging-1, staging-2, staging-3
  • do you need to define multiple header match rules on a route? And if so, are they And's or Or's? E.g. env: staging AND version: 2.

@mmosca
Copy link

mmosca commented Sep 19, 2020

This would be useful for us as well.

In our use case a simple case sensitive match for a single value would be enough, no wildcard needed.

In our scnario we send a custom header:

X-Environment: EnvironemntIdnetifier

The header value is a string, probably around 30 characters.

@jmezach
Copy link
Contributor

jmezach commented Sep 20, 2020

Our current environments are named TEST1 through TEST12 so I imagine they wouldn't have to be very long names and I would be fine with a case insensitive match. As for the more advanced scenario's I don't think we are going to need that any time soon and we would be happy with just a match on a single value as @mmosca mentioned.

@Kahbazi
Copy link
Collaborator

Kahbazi commented Sep 20, 2020

Another user case could be to prevent sending the requests to destination if certain headers are not present, like
Content-Type = application/json.

@Tratcher
Copy link
Member Author

Tratcher commented Sep 20, 2020

Hmm, content-type is a tricky example because it's a structured header, you'd at least need partial matching. E.g. application/json; charset=utf-8

@alnikola
Copy link
Contributor

We at Azure Quantum are also blocked by the unavailability of this feature.

We are expecting to use this feature to enable a dynamic fleet of short-lived service instances in a dev-test situation.

We envisage passing in a header - say x-ms-stagingenv with values of staging1 and staging2 for two staging environments, and our CI-CD pipeline will stand up two environments with corresponding names - say staging1.rp.azurewebsites.net and staging2.rp.azurewebsites.net.

We want to be able to dynamically construct the clusters and routes to be able to allow the routing of a fixed URL to either staging1 or staging2 (or indeed staging17) based on the value of x-ms-stagingenv.

We will presumably want to be able to deregister environments after they have been decommissioned, so we will likely want to use some form of database to look up registered environments; or, if there is a way to dynamically implement the routing rule itself, maybe dispense with any registration and directly route to a constructed URL.

It seems like a combination of dynamic routing and session affinity. However, I think you need to have only one cluster and then dynamically add/remove destinations as necessary.
Built-in session affinity logic binds a request sequence to a destination handled the first request which seems like what is needed here. You should only need to implement a custom destination (de)registering logic.

@Tratcher
Copy link
Member Author

Tratcher commented Sep 24, 2020

Design meeting notes:

Support matching against a list of header constraints which implicitly AND together (a request has to match all constraints).

  • OR can be implemented by using a separate route, or by pattern matching within the constraints.

A header constraint has the following characteristics:

  • The header name - always case insensitive, exact match.
  • The header value to match
  • Value case sensitivity - defaults to case insensitive, may need to be changed if you're matching something like Base64.
  • Single-value or multi-value - Only support single-value headers for now.
    • For multi-value you'd want to be able to scan a header and match against any one of its values.
  • The kind of match to perform:
    • exact
    • exists - any non-empty value is acceptable
    • These are some other match types we've theorized but are not currently implementing:
      • Starts With
      • pattern / regex
      • Context aware - A rule that understands the format of specific headers, parses them, and allows you to match against named fields. E.g. being able to write a rule to match the Content-Type header where the rule "application/*" matched both "application/json" and "application/xml; charset=utf-8".

Keeping in mind that we eventually want to transition this into AspNetCore 6.0, we should also consider what header constraint MVC attribute would look like.

  • One attribute per constraint with the fields listed above (name, value, case sensitivity, match type, etc.)
  • Multiple metadata entries (is that supported?)

Perf: AspNet routing supports a lot of complex perf optimizations like INodeBuilderPolicy. For the first pass we don't plan to implement many of those. Once we have the basic feature set we can optimize as needed.

@Tratcher Tratcher added Type: Enhancement New feature or request and removed Type: Bug Something isn't working labels Oct 6, 2020
@Tratcher Tratcher closed this as completed Oct 6, 2020
@samsp-msft samsp-msft added the User Story Used for divisional .NET planning label Oct 21, 2020
@samsp-msft samsp-msft changed the title Header based routing Proxy should enable routing based on header values Oct 22, 2020
@samsp-msft samsp-msft added the Priority:0 Used for divisional .NET planning label Jan 20, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Partner Blocking: Island Gateway Priority:0 Used for divisional .NET planning Type: Enhancement New feature or request User Story Used for divisional .NET planning
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants