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

#360 Routing based on values in designated upstream request headers #1684

Open
wants to merge 13 commits into
base: develop
Choose a base branch
from

Conversation

mrclayman
Copy link

@mrclayman mrclayman commented Jul 28, 2023

Closes #360

Addition of support for request re-routing based also on upstream header configuration as per issues

Proposed Changes

  • Implementation of support for re-routing based on request's header configuration
  • Update of relevant tests to cover the new functionality
  • Update of relevant documentation to make clear how to make use of the new functionality

Downstream URL placeholder replacement mentioned in issue #360 is not yet supported although I do have plans to add support for that as well.

@raman-m raman-m changed the title Implementation of support for routing based on values in designated u… #360 Routing based on values in designated upstream request headers Jul 31, 2023
@raman-m
Copy link
Member

raman-m commented Jul 31, 2023

Zbyněk, thanks for the great PR! 💪

Does the PR close the #624 ?
I know it is closed, but maybe some implementations of #624 are included into this PR ?!


I am a bit confused by this PR because you said that it makes little sense to me to keep working on this any longer.
Anyway, I welcome your PR.
You need to understand that code review of this PR will take more time as for regular one because both solutions should be compared after a classic review.
This situation of having 2 solutions for the same issue is quite strange. 😄 But it is OK, we will have this experience. 😉

@raman-m raman-m added feature A new feature needs feedback Issue is waiting on feedback before acceptance labels Jul 31, 2023
@raman-m
Copy link
Member

raman-m commented Jul 31, 2023

Zbyněk,
Please request code review manually from the people you've communicated to in #360, if it is required on your opinion.
As for me it is hard to track your network.

@mrclayman
Copy link
Author

mrclayman commented Jul 31, 2023

Well, you wanted to salvage my changes from the old branch, so I thought it shouldn't hurt to just to take them and put them in a new branch. 😄 At any rate, I have been thinking about the practical benefit of the current solution. You see, since users will still have to provide unique upstream URL for different downstreams, I thought that maybe we could consider allowing identical upstream URL, but differentiating the mappings based on the headers.It kind of makes sense in my head, but let me know what you and maybe also other maintainers think.

EDIT: Actually, just looking at the first post in #360 , it becomes clear that the above is a requirement, not just an option. I will have to look into allowing it as it looks like Ocelot currently won't allow multiple upstream routes to be the same.

@mrclayman
Copy link
Author

mrclayman commented Aug 5, 2023

Cheers for the feedback, @RaynaldM . I have integrated it along with a modification to the FileConfigurationFluentValidator to allow for multiple routes having the same upstream path but different routing header layout. Seems to work nicely on my end but please do check out the changes and let me know if anything doesn't sit. In the meantime, I should probably look into placeholder replacement as some people voiced interest in having that working with headers also.

@mrclayman
Copy link
Author

Having taken a look at how URL placeholder extraction and replacement works, it's quite obvious that the current algorithm (implemented in UrlPathPlaceholderNameAndValueFinder) has been designed to work with URL's (routes and query arguments), which is suboptimal for header values as those usually don't contain either. I suppose I would therefore look into adapting some of the parts of the parser for use outside of URL patterns so that they could be reused in header value parser as well. It's just an initial idea that I have not thought a lot about, but let me know your take so we can agree on something. 👍

Copy link
Member

@raman-m raman-m left a comment

Choose a reason for hiding this comment

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

@mrclayman Hi Zbyněk!
Some suggestions:

  • Use file-scoped namespaces for new added files always, because we have .NET 6+ environment
  • Remove and Sort Usings by standard refactoring action in Visual Studio. I see you use some other RAD tool.
  • Use var option to define a variable for reference types

Don't judge me much while resolving these issues please! 🤣 👇


Helpful unit tests:

docs/features/routing.rst Outdated Show resolved Hide resolved
docs/features/routing.rst Outdated Show resolved Hide resolved
docs/features/routing.rst Outdated Show resolved Hide resolved
docs/features/routing.rst Outdated Show resolved Hide resolved
@raman-m
Copy link
Member

raman-m commented Aug 14, 2023

@mrclayman commented on Aug 7, 2023

This is crazy idea!
I saw a lot of PRs and issues in the backlog which change and/or fix the logic of placeholders. So, placeholders-related logic is quite unstable, it has some unclear bug fixes, and not documented well...
Your refactoring idea can be a challenge for you! 🤣
Should I say "Good luck"? 😄


Why not to fix code review issues first? 😉
And, don't forget about @RaynaldM's code review suggestions too! 😺

@mrclayman
Copy link
Author

mrclayman commented Aug 16, 2023

@raman-m requested changes on Aug 14

I work in Linux. There is no Visual Studio for that. I have already had to disable .editorconfig because apparently Rider cares about it a lot more than Visual Studio judging by the awful mixture of line endings across the code base. I'll see if I can set Rider to not reorder usings but can't make any promises about that.

@mrclayman
Copy link
Author

Integrated your PR feedback, @raman-m . See 6d29040 and let me know what you think. I tried to cover all of it so here's hoping I have succeeded at that. 🤞

@mrclayman
Copy link
Author

Ping @raman-m, hope you didn't fall off the edge of the earth. 🙂

@raman-m
Copy link
Member

raman-m commented Sep 21, 2023

@raman-m requested changes on Aug 14

@mrclayman I've resolved all issues of the 1st code review.
The 2nd code review is ongoing...

Consider renaming UpstreamHeaderRoutingOptions class by suffix Options removing. Also, JSON property should be shorten like UpstreamHeaderRouting. What do you think?

@mrclayman
Copy link
Author

mrclayman commented Sep 22, 2023

@raman-m requested changes on Aug 14

@mrclayman I've resolved all issues of the 1st code review. The 2nd code review is ongoing...

Consider renaming UpstreamHeaderRoutingOptions class by suffix Options removing. Also, JSON property should be shorten like UpstreamHeaderRouting. What do you think?

There is already a class called UpstreamRoutingHeaders. IMO having UpstreamHeaderRouting would be confusing. If I were to rename something, it would be UpstreamRoutingHeaders -> UpstreamHeaderRouting and I would leave the Options class as is. 🤔

At any rate, about to commit my changes. Once you explain to me how you intended me to fix the member/local variable question, I can consider that.

@raman-m raman-m added the conflicts Feature branch has merge conflicts label Oct 9, 2023
@raman-m
Copy link
Member

raman-m commented Oct 9, 2023

@mrclayman Conflicts in usings block mostly!
Use Remove and Sort Usings refactoring operation of Visual Studio.
So, usings must be sorted!
And pay attention, we have global usings now in Usings.cs file

@raman-m
Copy link
Member

raman-m commented Oct 9, 2023

@mrclayman commented 2 weeks ago

Also why Upstream- prefix? Do we have DownstreamHeaderRouting? It seems Not.
Also, HeaderRouting could be a good name for the feature.

@mrclayman
Copy link
Author

@mrclayman Conflicts in usings block mostly! Use Remove and Sort Usings refactoring operation of Visual Studio. So, usings must be sorted! And pay attention, we have global usings now in Usings.cs file

I have already told you, I am NOT using Windows and therefore Visual Studio. Right now, I am desperately trying to find some merge tool that does not f*ck up line endings.

@mrclayman
Copy link
Author

All right, I have rebased this branch against the current state of develop. Tried my best with regard to the line endings.

@mrclayman
Copy link
Author

mrclayman commented Oct 11, 2023

As for the Upstream prefix, I prefer clarity over intuition and I would like the prefix to stay -- both in configuration and the code. Feel free to rename it if you so desire. And as for the rest of the code, take it or leave it. I am done with this whole thing.

@raman-m
Copy link
Member

raman-m commented Oct 30, 2023

@mrclayman commented on Oct 9

🆗 Got it! Try to use Visual Code if your workplace is Linux!
Visual Code should be tolerant to line-endings of C# files in .NET projects.

@raman-m
Copy link
Member

raman-m commented Oct 30, 2023

@mrclayman commented on Oct 11

Sorry, for being silent for 3 weeks. Our team is tiny: a few people only.

I'd like to postpone this thread because of 30+ open PRs with lower number.
I'll return to you back in a couple of months...


I am done with this whole thing.

Thanks for your great contribution! 👍

@raman-m raman-m removed the conflicts Feature branch has merge conflicts label Oct 30, 2023
@raman-m raman-m added Routing Ocelot feature: Routing and removed needs feedback Issue is waiting on feedback before acceptance labels Feb 17, 2024
Comment on lines +51 to +61
private static IHeaderDictionary NormalizeHeaderNames(IHeaderDictionary headers)
{
var upperCaseHeaders = new HeaderDictionary();
foreach (KeyValuePair<string, StringValues> kv in headers)
{
var key = kv.Key.ToUpperInvariant();
upperCaseHeaders.Add(key, kv.Value);
}

return upperCaseHeaders;
}
Copy link
Collaborator

@RaynaldM RaynaldM Feb 19, 2024

Choose a reason for hiding this comment

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

Suggested change
private static IHeaderDictionary NormalizeHeaderNames(IHeaderDictionary headers)
{
var upperCaseHeaders = new HeaderDictionary();
foreach (KeyValuePair<string, StringValues> kv in headers)
{
var key = kv.Key.ToUpperInvariant();
upperCaseHeaders.Add(key, kv.Value);
}
return upperCaseHeaders;
}
private static IHeaderDictionary NormalizeHeaderNames(IHeaderDictionary headers) =>
new HeaderDictionary(headers.ToDictionary(
h => h.Key.ToUpperInvariant(),
h => h.Value
));

public bool HasAllOf(IHeaderDictionary requestHeaders)
{
IHeaderDictionary normalizedHeaders = NormalizeHeaderNames(requestHeaders);
foreach (var h in Headers)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
foreach (var h in Headers)
foreach (var header in Headers)

public bool HasAnyOf(IHeaderDictionary requestHeaders)
{
IHeaderDictionary normalizedHeaders = NormalizeHeaderNames(requestHeaders);
foreach (var h in Headers)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
foreach (var h in Headers)
foreach (var header in Headers)


public class UpstreamRoutingHeaders
{
public IReadOnlyDictionary<string, ICollection<string>> Headers { get; }
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
public IReadOnlyDictionary<string, ICollection<string>> Headers { get; }
public readonly IReadOnlyDictionary<string, ICollection<string>> Headers { get; }

could be private ?

Comment on lines +19 to +27
IHeaderDictionary normalizedHeaders = NormalizeHeaderNames(requestHeaders);
foreach (var h in Headers)
{
if (normalizedHeaders.TryGetValue(h.Key, out var values) &&
h.Value.Intersect(values, StringComparer.OrdinalIgnoreCase).Any())
{
return true;
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
IHeaderDictionary normalizedHeaders = NormalizeHeaderNames(requestHeaders);
foreach (var h in Headers)
{
if (normalizedHeaders.TryGetValue(h.Key, out var values) &&
h.Value.Intersect(values, StringComparer.OrdinalIgnoreCase).Any())
{
return true;
}
}
var normalizedHeaders = NormalizeHeaderNames(requestHeaders);
foreach (var (key, value) in Headers)
{
if (normalizedHeaders.TryGetValue(key, out var values))
{
if (value.Any(item => values.Contains(item, StringComparer.OrdinalIgnoreCase)))
{
return true;
}
}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature A new feature Routing Ocelot feature: Routing
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Routing based on Request Header
3 participants