-
Notifications
You must be signed in to change notification settings - Fork 855
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Request can be affinitized to destinations (#174)
Request can be affinitized to destinations by an affinity key extracted from cookie or custom header. Affinity is active only when load balancing is enabled and it can be configured for each backend independently. One request can have affinity to a single destination or to a set. It enables scenarios where a session needs to be affinitized to a pool of load-balanced destinations. Affinity failures are handled by a policy configured per backend which has full access to `HttpContext` and decides whether the request processing can continue or not. Fixes #45
- Loading branch information
Showing
48 changed files
with
2,045 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.Linq; | ||
using System.Net; | ||
using System.Net.Http; | ||
using System.Text; | ||
using System.Text.Json; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace SampleClient.Scenarios | ||
{ | ||
internal class SessionAffinityScenario : IScenario | ||
{ | ||
public async Task ExecuteAsync(CommandLineArgs args, CancellationToken cancellation) | ||
{ | ||
using var handler = new HttpClientHandler | ||
{ | ||
AllowAutoRedirect = false, | ||
AutomaticDecompression = DecompressionMethods.None, | ||
// Session affinity key will be stored in a cookie | ||
UseCookies = true, | ||
UseProxy = false | ||
}; | ||
using var client = new HttpMessageInvoker(handler); | ||
var targetUri = new Uri(new Uri(args.Target, UriKind.Absolute), "api/dump"); | ||
var stopwatch = Stopwatch.StartNew(); | ||
|
||
var request0 = new HttpRequestMessage(HttpMethod.Get, targetUri) { Version = new Version(1, 1) }; | ||
Console.WriteLine($"Sending first request to {targetUri} with HTTP/1.1"); | ||
var response0 = await client.SendAsync(request0, cancellation); | ||
|
||
PrintDuration(stopwatch, response0); | ||
PrintAffinityCookie(handler, targetUri, response0); | ||
await ReadAndPrintBody(response0, cancellation); | ||
|
||
stopwatch.Reset(); | ||
|
||
var request1 = new HttpRequestMessage(HttpMethod.Get, targetUri) { Version = new Version(1, 1) }; | ||
Console.WriteLine($"Sending second request to {targetUri} with HTTP/1.1"); | ||
var response1 = await client.SendAsync(request1, cancellation); | ||
|
||
PrintDuration(stopwatch, response1); | ||
PrintAffinityCookie(handler, targetUri, response1); | ||
await ReadAndPrintBody(response1, cancellation); | ||
} | ||
|
||
private static void PrintDuration(Stopwatch stopwatch, HttpResponseMessage response) | ||
{ | ||
Console.WriteLine($"Received response: {(int)response.StatusCode} in {stopwatch.ElapsedMilliseconds} ms"); | ||
response.EnsureSuccessStatusCode(); | ||
} | ||
|
||
private static void PrintAffinityCookie(HttpClientHandler handler, Uri targetUri, HttpResponseMessage response) | ||
{ | ||
if (response.Headers.TryGetValues("Set-Cookie", out var setCookieValue)) | ||
{ | ||
Console.WriteLine($"Received header Set-Cookie: {setCookieValue.ToArray()[0]}"); | ||
} | ||
else | ||
{ | ||
Console.WriteLine($"Response doesn't have Set-Cookie header."); | ||
} | ||
|
||
var affinityCookie = handler.CookieContainer.GetCookies(targetUri)[".Microsoft.ReverseProxy.Affinity"]; | ||
Console.WriteLine($"Affinity key stored on a cookie {affinityCookie.Value}"); | ||
} | ||
|
||
private static async Task ReadAndPrintBody(HttpResponseMessage response, CancellationToken cancellation) | ||
{ | ||
var body = await response.Content.ReadAsStringAsync(cancellation); | ||
var json = JsonDocument.Parse(body); | ||
Console.WriteLine( | ||
"Received response:" + | ||
$"{Environment.NewLine}" + | ||
$"{JsonSerializer.Serialize(json.RootElement, new JsonSerializerOptions { WriteIndented = true })}"); | ||
response.EnsureSuccessStatusCode(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
...verseProxy/Abstractions/BackendDiscovery/Contract/CookieSessionAffinityProviderOptions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
using Microsoft.AspNetCore.Http; | ||
|
||
namespace Microsoft.ReverseProxy.Abstractions.BackendDiscovery.Contract | ||
{ | ||
/// <summary> | ||
/// Defines cookie-specific affinity provider options. | ||
/// </summary> | ||
public class CookieSessionAffinityProviderOptions | ||
{ | ||
private CookieBuilder _cookieBuilder = new AffinityCookieBuilder(); | ||
|
||
public static readonly string DefaultCookieName = ".Microsoft.ReverseProxy.Affinity"; | ||
|
||
public CookieBuilder Cookie | ||
{ | ||
get => _cookieBuilder; | ||
set => _cookieBuilder = value ?? throw new ArgumentNullException(nameof(value)); | ||
} | ||
|
||
private class AffinityCookieBuilder : CookieBuilder | ||
{ | ||
public AffinityCookieBuilder() | ||
{ | ||
Name = DefaultCookieName; | ||
SecurePolicy = CookieSecurePolicy.None; | ||
SameSite = SameSiteMode.Unspecified; | ||
HttpOnly = true; | ||
IsEssential = false; | ||
} | ||
|
||
public override TimeSpan? Expiration | ||
{ | ||
get => null; | ||
set => throw new InvalidOperationException(nameof(Expiration) + " cannot be set for the cookie defined by " + nameof(CookieSessionAffinityProviderOptions)); | ||
} | ||
} | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
src/ReverseProxy/Abstractions/BackendDiscovery/Contract/SessionAffinityConstants.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
namespace Microsoft.ReverseProxy.Abstractions.BackendDiscovery.Contract | ||
{ | ||
/// <summary> | ||
/// Names of built-in session affinity services. | ||
/// </summary> | ||
public static class SessionAffinityConstants | ||
{ | ||
public static class Modes | ||
{ | ||
public static string Cookie => "Cookie"; | ||
|
||
public static string CustomHeader => "CustomHeader"; | ||
} | ||
|
||
public static class AffinityFailurePolicies | ||
{ | ||
public static string Redistribute => "Redistribute"; | ||
|
||
public static string Return503Error => "Return503Error"; | ||
} | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
src/ReverseProxy/Abstractions/BackendDiscovery/Contract/SessionAffinityOptions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
|
||
namespace Microsoft.ReverseProxy.Abstractions.BackendDiscovery.Contract | ||
{ | ||
/// <summary> | ||
/// Session affinitity options. | ||
/// </summary> | ||
public sealed class SessionAffinityOptions | ||
{ | ||
/// <summary> | ||
/// Indicates whether session affinity is enabled. | ||
/// </summary> | ||
public bool Enabled { get; set; } | ||
|
||
/// <summary> | ||
/// Session affinity mode which is implemented by one of providers. | ||
/// </summary> | ||
public string Mode { get; set; } | ||
|
||
/// <summary> | ||
/// Strategy handling missing destination for an affinitized request. | ||
/// </summary> | ||
public string AffinityFailurePolicy { get; set; } | ||
|
||
/// <summary> | ||
/// Key-value pair collection holding extra settings specific to different affinity modes. | ||
/// </summary> | ||
public IDictionary<string, string> Settings { get; set; } | ||
|
||
internal SessionAffinityOptions DeepClone() | ||
{ | ||
return new SessionAffinityOptions | ||
{ | ||
Enabled = Enabled, | ||
Mode = Mode, | ||
AffinityFailurePolicy = AffinityFailurePolicy, | ||
Settings = Settings?.DeepClone(StringComparer.Ordinal) | ||
}; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.