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

#1314 #1869 #2072 Default timeout enhancements #2073

Open
wants to merge 50 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
6dc18ae
RequestTimeoutSeconds FileGlobalConfiguration
hogwartsdeveloper May 25, 2024
e4769be
Fix Test
hogwartsdeveloper May 25, 2024
972d88c
Fix Remarks
hogwartsdeveloper May 26, 2024
03edc49
Add DI FileGlobalConfiguration. PollyQoSProvider, PollyQoSResilienceP…
hogwartsdeveloper May 27, 2024
c85d6a2
Fix Remarks
hogwartsdeveloper May 28, 2024
697f8b9
Fix remarks
hogwartsdeveloper May 28, 2024
295b4f2
Add DownStream Timeout
hogwartsdeveloper May 29, 2024
4d28961
Use One Timeout
hogwartsdeveloper May 29, 2024
b164cc8
Fix
hogwartsdeveloper May 29, 2024
2f15ba8
Fix Remarks
hogwartsdeveloper May 29, 2024
27f5f5c
Nullable timeouts
hogwartsdeveloper May 30, 2024
054ea32
Remove TimeoutCreator
hogwartsdeveloper May 30, 2024
7680e77
RoutesCreator CreateTimeout
hogwartsdeveloper May 30, 2024
7e574f7
PollyQoSResiliencePipelineProvider Return Old Logic And if-condition
hogwartsdeveloper May 30, 2024
b270e6a
Fix
hogwartsdeveloper May 30, 2024
1c37135
Fix Remarks
hogwartsdeveloper Jun 1, 2024
ad69a80
Fix remarks
hogwartsdeveloper Jun 2, 2024
f4dc07c
Fix
hogwartsdeveloper Jun 2, 2024
c21b1ca
Fix Tests
hogwartsdeveloper Jun 2, 2024
2b5e943
Update src/Ocelot/Configuration/File/FileRoute.cs
hogwartsdeveloper Jun 1, 2024
0a8c77c
Fix build error
hogwartsdeveloper Jun 2, 2024
80123c8
Try to fix... failed
raman-m Jun 3, 2024
fe6c3c4
Fix Tests
hogwartsdeveloper Jun 4, 2024
76471c2
MessageInvokerPoolTests
hogwartsdeveloper Jun 4, 2024
12d8659
RoutesCreatorTests
hogwartsdeveloper Jun 4, 2024
30e4256
Review fixed/new unit tests
raman-m Jun 4, 2024
830f029
Remove BDDfy from unit tests
raman-m Jun 4, 2024
567ae5c
Review `MessageInvokerPoolTests` and AAA pattern
raman-m Jun 4, 2024
ae638b1
Fix test : `Should_timeout_per_default_after_90_seconds`
raman-m Jun 4, 2024
62336fa
More refactoring: QoS vs DynRoute timeouts
raman-m Jun 4, 2024
6ab5355
Polly constraints
raman-m Jun 6, 2024
046bd51
Fix SA warnings
raman-m Oct 14, 2024
6f763ee
Inherit from Steps
raman-m Oct 14, 2024
7cb4c6e
The `HttpMessageInvoker` retrieves the timeout value from `Downstream…
raman-m Oct 14, 2024
62f8dcd
Reduce timeouts in unit tests to improve execution speed
raman-m Oct 14, 2024
22adbae
add user message
raman-m Oct 14, 2024
87723dd
let's be more precise
raman-m Oct 14, 2024
c4ea6fb
100ms margin should be fine
raman-m Oct 14, 2024
52d84e3
Assert timeout precisely
raman-m Oct 15, 2024
b6741bc
Test each failed attempt with an incremented margin.
raman-m Oct 15, 2024
a0a7c49
Fix build after rebasing
raman-m Oct 16, 2024
a51bb31
`MessageInvokerPool` shouldn't have internal state for timeouts becau…
raman-m Oct 17, 2024
6950f0e
Review logic for disabled test parallelization
raman-m Oct 17, 2024
47793c1
EOL: src/Ocelot/Configuration/Creator/RoutesCreator.cs
raman-m Oct 17, 2024
14de21b
EOL: src/Ocelot/Configuration/DownstreamRoute.cs
raman-m Oct 17, 2024
0af9423
EOL: src/Ocelot/Configuration/File/FileRoute.cs
raman-m Oct 17, 2024
04d2b5b
EOL: test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs
raman-m Oct 17, 2024
cfd0759
Review `FileGlobalConfiguration`
raman-m Oct 17, 2024
db1fe0a
Fix build after rebasing
raman-m Nov 1, 2024
2810c03
Fix failed tests
raman-m Nov 1, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 29 additions & 11 deletions src/Ocelot.Provider.Polly/PollyQoSResiliencePipelineProvider.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Microsoft.Extensions.Options;
using Ocelot.Configuration;
using Ocelot.Configuration.File;
using Ocelot.Logging;
using Ocelot.Provider.Polly.Interfaces;
using Polly.CircuitBreaker;
Expand All @@ -15,13 +17,16 @@ public class PollyQoSResiliencePipelineProvider : IPollyQoSResiliencePipelinePro
{
private readonly ResiliencePipelineRegistry<OcelotResiliencePipelineKey> _registry;
private readonly IOcelotLogger _logger;

private readonly FileGlobalConfiguration _global;

public PollyQoSResiliencePipelineProvider(
IOcelotLoggerFactory loggerFactory,
ResiliencePipelineRegistry<OcelotResiliencePipelineKey> registry)
ResiliencePipelineRegistry<OcelotResiliencePipelineKey> registry,
IOptions<FileGlobalConfiguration> global)
raman-m marked this conversation as resolved.
Show resolved Hide resolved
{
_logger = loggerFactory.CreateLogger<PollyQoSResiliencePipelineProvider>();
_registry = registry;
_global = global.Value;
}

protected static readonly HashSet<HttpStatusCode> DefaultServerErrorCodes = new()
Expand Down Expand Up @@ -74,14 +79,21 @@ protected virtual ResiliencePipelineBuilder<HttpResponseMessage> ConfigureCircui

var options = route.QosOptions;
var info = $"Circuit Breaker for the route: {GetRouteName(route)}: ";

// Polly constraints
int minimumThroughput = options.ExceptionsAllowedBeforeBreaking >= QoSOptions.LowMinimumThroughput
? options.ExceptionsAllowedBeforeBreaking
: QoSOptions.DefaultMinimumThroughput;
int breakDurationMs = options.DurationOfBreak > QoSOptions.LowBreakDuration
? options.DurationOfBreak
: QoSOptions.DefaultBreakDuration;

var strategyOptions = new CircuitBreakerStrategyOptions<HttpResponseMessage>
{
FailureRatio = 0.8,
SamplingDuration = TimeSpan.FromSeconds(10),
MinimumThroughput = options.ExceptionsAllowedBeforeBreaking,
BreakDuration = options.DurationOfBreak > QoSOptions.LowBreakDuration
? TimeSpan.FromMilliseconds(options.DurationOfBreak)
: TimeSpan.FromMilliseconds(QoSOptions.DefaultBreakDuration),
MinimumThroughput = minimumThroughput,
BreakDuration = TimeSpan.FromMilliseconds(breakDurationMs),
ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
.HandleResult(message => ServerErrorCodes.Contains(message.StatusCode))
.Handle<TimeoutRejectedException>()
Expand All @@ -108,18 +120,24 @@ protected virtual ResiliencePipelineBuilder<HttpResponseMessage> ConfigureCircui

protected virtual ResiliencePipelineBuilder<HttpResponseMessage> ConfigureTimeout(ResiliencePipelineBuilder<HttpResponseMessage> builder, DownstreamRoute route)
{
var options = route.QosOptions;
int? timeoutMs = route?.QosOptions?.TimeoutValue
?? _global?.QoSOptions?.TimeoutValue
?? QoSOptions.DefaultTimeout;

// Add Timeout strategy if TimeoutValue is not int.MaxValue and greater than 0
// TimeoutValue must be defined in QosOptions!
if (options.TimeoutValue == int.MaxValue || options.TimeoutValue <= 0)
// 0 means No option!
if (!timeoutMs.HasValue || timeoutMs.Value <= 0)
{
return builder;
}

// Polly constraints
timeoutMs = timeoutMs.Value > QoSOptions.LowTimeout && timeoutMs.Value < QoSOptions.HighTimeout
? timeoutMs.Value
: QoSOptions.DefaultTimeout;

var strategyOptions = new TimeoutStrategyOptions
{
Timeout = TimeSpan.FromMilliseconds(options.TimeoutValue),
Timeout = TimeSpan.FromMilliseconds(timeoutMs.Value),
OnTimeout = _ =>
{
_logger.LogInformation(() => $"Timeout for the route: {GetRouteName(route)}");
Expand Down
10 changes: 9 additions & 1 deletion src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public class DownstreamRouteBuilder
private HttpVersionPolicy _downstreamHttpVersionPolicy;
private Dictionary<string, UpstreamHeaderTemplate> _upstreamHeaders;
private MetadataOptions _metadataOptions;
private int? _timeout;

public DownstreamRouteBuilder()
{
Expand Down Expand Up @@ -282,6 +283,12 @@ public DownstreamRouteBuilder WithMetadata(MetadataOptions metadataOptions)
return this;
}

public DownstreamRouteBuilder WithTimeout(int? timeout)
{
_timeout = timeout;
return this;
}

public DownstreamRoute Build()
{
return new DownstreamRoute(
Expand Down Expand Up @@ -321,6 +328,7 @@ public DownstreamRoute Build()
_downstreamHttpVersion,
_downstreamHttpVersionPolicy,
_upstreamHeaders,
_metadataOptions);
_metadataOptions,
_timeout);
}
}
4 changes: 2 additions & 2 deletions src/Ocelot/Configuration/Builder/QoSOptionsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public class QoSOptionsBuilder

private int _durationOfBreak;

private int _timeoutValue;
private int? _timeoutValue;

private string _key;

Expand All @@ -22,7 +22,7 @@ public QoSOptionsBuilder WithDurationOfBreak(int durationOfBreak)
return this;
}

public QoSOptionsBuilder WithTimeoutValue(int timeoutValue)
public QoSOptionsBuilder WithTimeoutValue(int? timeoutValue)
{
_timeoutValue = timeoutValue;
return this;
Expand Down
18 changes: 11 additions & 7 deletions src/Ocelot/Configuration/Creator/DynamicsCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,26 @@ public List<Route> Create(FileConfiguration fileConfiguration)
.ToList();
}

private Route SetUpDynamicRoute(FileDynamicRoute fileDynamicRoute, FileGlobalConfiguration globalConfiguration)
public int CreateTimeout(FileDynamicRoute route, FileGlobalConfiguration global)
=> route.Timeout ?? global.Timeout ?? DownstreamRoute.DefaultTimeoutSeconds;

private Route SetUpDynamicRoute(FileDynamicRoute dynamicRoute, FileGlobalConfiguration globalConfiguration)
{
var rateLimitOption = _rateLimitOptionsCreator
.Create(fileDynamicRoute.RateLimitRule, globalConfiguration);
.Create(dynamicRoute.RateLimitRule, globalConfiguration);

var version = _versionCreator.Create(fileDynamicRoute.DownstreamHttpVersion);
var versionPolicy = _versionPolicyCreator.Create(fileDynamicRoute.DownstreamHttpVersionPolicy);
var metadata = _metadataCreator.Create(fileDynamicRoute.Metadata, globalConfiguration);
var version = _versionCreator.Create(dynamicRoute.DownstreamHttpVersion);
var versionPolicy = _versionPolicyCreator.Create(dynamicRoute.DownstreamHttpVersionPolicy);
var metadata = _metadataCreator.Create(dynamicRoute.Metadata, globalConfiguration);

var downstreamRoute = new DownstreamRouteBuilder()
.WithEnableRateLimiting(rateLimitOption.EnableRateLimiting)
.WithRateLimitOptions(rateLimitOption)
.WithServiceName(fileDynamicRoute.ServiceName)
.WithServiceName(dynamicRoute.ServiceName)
.WithDownstreamHttpVersion(version)
.WithDownstreamHttpVersionPolicy(versionPolicy)
.WithMetadata(metadata)
.WithMetadata(metadata)
.WithTimeout(CreateTimeout(dynamicRoute, globalConfiguration))
.Build();

var route = new RouteBuilder()
Expand Down
8 changes: 8 additions & 0 deletions src/Ocelot/Configuration/Creator/IDynamicsCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,13 @@ namespace Ocelot.Configuration.Creator
public interface IDynamicsCreator
{
List<Route> Create(FileConfiguration fileConfiguration);

/// <summary>
/// Creates a timeout value for a given file route based on the global configuration.
/// </summary>
/// <param name="route">The file route for which to create the timeout.</param>
/// <param name="global">The global configuration to use for creating the timeout.</param>
/// <returns>The timeout value in seconds.</returns>
int CreateTimeout(FileDynamicRoute route, FileGlobalConfiguration global);
}
}
8 changes: 8 additions & 0 deletions src/Ocelot/Configuration/Creator/IRoutesCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,12 @@ namespace Ocelot.Configuration.Creator;
public interface IRoutesCreator
{
List<Route> Create(FileConfiguration fileConfiguration);

/// <summary>
/// Creates a timeout value for a given file route based on the global configuration.
/// </summary>
/// <param name="route">The file route for which to create the timeout.</param>
/// <param name="global">The global configuration to use for creating the timeout.</param>
/// <returns>The timeout value in seconds.</returns>
int CreateTimeout(FileRoute route, FileGlobalConfiguration global);
}
2 changes: 1 addition & 1 deletion src/Ocelot/Configuration/Creator/QoSOptionsCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public QoSOptions Create(QoSOptions options, string pathTemplate, List<string> h
return Map(key, options.TimeoutValue, options.DurationOfBreak, options.ExceptionsAllowedBeforeBreaking);
}

private static QoSOptions Map(string key, int timeoutValue, int durationOfBreak, int exceptionsAllowedBeforeBreaking)
private static QoSOptions Map(string key, int? timeoutValue, int durationOfBreak, int exceptionsAllowedBeforeBreaking)
{
return new QoSOptionsBuilder()
.WithExceptionsAllowedBeforeBreaking(exceptionsAllowedBeforeBreaking)
Expand Down
14 changes: 7 additions & 7 deletions src/Ocelot/Configuration/Creator/RoutesCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,16 @@ public RoutesCreator(

public List<Route> Create(FileConfiguration fileConfiguration)
{
Route CreateRoute(FileRoute route)
=> SetUpRoute(route, SetUpDownstreamRoute(route, fileConfiguration.GlobalConfiguration));
return fileConfiguration.Routes
.Select(route =>
{
var downstreamRoute = SetUpDownstreamRoute(route, fileConfiguration.GlobalConfiguration);
return SetUpRoute(route, downstreamRoute);
})
.Select(CreateRoute)
.ToList();
}

public int CreateTimeout(FileRoute route, FileGlobalConfiguration global)
=> route.Timeout ?? global.Timeout ?? DownstreamRoute.DefaultTimeoutSeconds;

private DownstreamRoute SetUpDownstreamRoute(FileRoute fileRoute, FileGlobalConfiguration globalConfiguration)
{
var fileRouteOptions = _fileRouteOptionsCreator.Create(fileRoute);
Expand Down Expand Up @@ -156,8 +157,8 @@ private DownstreamRoute SetUpDownstreamRoute(FileRoute fileRoute, FileGlobalConf
.WithDownstreamHttpVersionPolicy(downstreamHttpVersionPolicy)
.WithDownStreamHttpMethod(fileRoute.DownstreamHttpMethod)
.WithMetadata(metadata)
.WithTimeout(CreateTimeout(fileRoute, globalConfiguration))
.Build();

return route;
}

Expand All @@ -173,7 +174,6 @@ private Route SetUpRoute(FileRoute fileRoute, DownstreamRoute downstreamRoutes)
.WithUpstreamHost(fileRoute.UpstreamHost)
.WithUpstreamHeaders(upstreamHeaderTemplates)
.Build();

return route;
}
}
Expand Down
21 changes: 20 additions & 1 deletion src/Ocelot/Configuration/DownstreamRoute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ public DownstreamRoute(
Version downstreamHttpVersion,
HttpVersionPolicy downstreamHttpVersionPolicy,
Dictionary<string, UpstreamHeaderTemplate> upstreamHeaders,
MetadataOptions metadataOptions)
MetadataOptions metadataOptions,
int? timeout)
{
DangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator;
AddHeadersToDownstream = addHeadersToDownstream;
Expand Down Expand Up @@ -81,6 +82,7 @@ public DownstreamRoute(
DownstreamHttpVersionPolicy = downstreamHttpVersionPolicy;
UpstreamHeaders = upstreamHeaders ?? new();
MetadataOptions = metadataOptions;
Timeout = timeout;
raman-m marked this conversation as resolved.
Show resolved Hide resolved
}

public string Key { get; }
Expand Down Expand Up @@ -137,5 +139,22 @@ public DownstreamRoute(
public string Name() => string.IsNullOrEmpty(ServiceName) && !UseServiceDiscovery
? UpstreamPathTemplate?.Template ?? DownstreamPathTemplate?.Value ?? "?"
: string.Join(':', ServiceNamespace, ServiceName, UpstreamPathTemplate?.Template);

/// <summary>The timeout duration for the downstream request in seconds.</summary>
/// <value>A <see cref="Nullable{T}"/> (T is <see cref="int"/>) value, in seconds.</value>
public int? Timeout { get; }

/// <summary>Defines the default timeout in seconds for all routes, applicable at both the Route-level and globally.</summary>
/// <remarks>By default, initialized to 90 seconds.</remarks>
/// <value>An <see cref="int"/> value.</value>
public static int DefaultTimeoutSeconds { get; set; } = 90;

/// <summary>
/// Calculates timeout in milliseconds based on QoS options with applying default timeout values.
/// </summary>
/// <returns>An <see cref="int"/> value, in milliseconds.</returns>
public int TimeoutMilliseconds() => QosOptions.UseQos
? QosOptions.TimeoutValue ?? QoSOptions.DefaultTimeout
: 1000 * (Timeout ?? DefaultTimeoutSeconds);
}
}
7 changes: 7 additions & 0 deletions src/Ocelot/Configuration/File/FileDynamicRoute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace Ocelot.Configuration.File
{
/// <summary>
/// TODO: Make it as a base Route File-model.
/// </summary>
public class FileDynamicRoute
{
public string ServiceName { get; set; }
Expand All @@ -20,5 +23,9 @@ public class FileDynamicRoute
/// </remarks>
public string DownstreamHttpVersionPolicy { get; set; }
public IDictionary<string, string> Metadata { get; set; }

/// <summary>The timeout in seconds for requests.</summary>
/// <value>A <see cref="Nullable{T}"/> where T is <see cref="int"/> value in seconds.</value>
public int? Timeout { get; set; }
}
}
4 changes: 4 additions & 0 deletions src/Ocelot/Configuration/File/FileGlobalConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ public FileGlobalConfiguration()

public string RequestIdKey { get; set; }

/// <summary>The timeout in seconds for requests.</summary>
/// <value>A <see cref="Nullable{T}"/> (T is <see cref="int"/>) value in seconds.</value>
public int? Timeout { get; set; }

public FileServiceDiscoveryProvider ServiceDiscoveryProvider { get; set; }

public FileRateLimitOptions RateLimitOptions { get; set; }
Expand Down
15 changes: 13 additions & 2 deletions src/Ocelot/Configuration/File/FileQoSOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public FileQoSOptions()
{
DurationOfBreak = 1;
ExceptionsAllowedBeforeBreaking = 0;
TimeoutValue = 0;
TimeoutValue = null; // default value will be assigned in consumer services: see DownstreamRoute.
}

public FileQoSOptions(FileQoSOptions from)
Expand All @@ -32,6 +32,17 @@ public FileQoSOptions(QoSOptions from)

public int DurationOfBreak { get; set; }
public int ExceptionsAllowedBeforeBreaking { get; set; }
public int TimeoutValue { get; set; }

/// <summary>Explicit timeout value which overrides default one.</summary>
/// <remarks>Reused in, or ignored in favor of implicit default value:
/// <list type="bullet">
/// <item><see cref="QoSOptions.TimeoutValue"/></item>
/// <item><see cref="DownstreamRoute.Timeout"/></item>
/// <item><see cref="DownstreamRoute.TimeoutMilliseconds"/></item>
/// <item><see cref="DownstreamRoute.DefaultTimeoutSeconds"/></item>
/// </list>
/// </remarks>
/// <value>A <see cref="Nullable{T}"/> (T is <see cref="int"/>) value in milliseconds.</value>
public int? TimeoutValue { get; set; }
raman-m marked this conversation as resolved.
Show resolved Hide resolved
}
}
19 changes: 15 additions & 4 deletions src/Ocelot/Configuration/File/FileRoute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Ocelot.Configuration.File
{
public class FileRoute : IRoute, ICloneable
public class FileRoute : IRoute, ICloneable // TODO: Inherit from FileDynamicRoute (FileRouteBase) or an interface with FileDynamicRoute props
{
public FileRoute()
{
Expand Down Expand Up @@ -72,8 +72,19 @@ public FileRoute(FileRoute from)
public FileSecurityOptions SecurityOptions { get; set; }
public string ServiceName { get; set; }
public string ServiceNamespace { get; set; }
public int Timeout { get; set; }
public Dictionary<string, string> UpstreamHeaderTransform { get; set; }

/// <summary>Explicit timeout value which overrides default one.</summary>
/// <remarks>Reused in, or ignored in favor of implicit default value:
/// <list type="bullet">
/// <item><see cref="DownstreamRoute.Timeout"/></item>
/// <item><see cref="DownstreamRoute.TimeoutMilliseconds"/></item>
/// <item><see cref="DownstreamRoute.DefaultTimeoutSeconds"/></item>
/// </list>
/// </remarks>
/// <value>A <see cref="Nullable{T}"/> (T is <see cref="int"/>) value, in seconds.</value>
public int? Timeout { get; set; }
raman-m marked this conversation as resolved.
Show resolved Hide resolved

public IDictionary<string, string> UpstreamHeaderTransform { get; set; }
public string UpstreamHost { get; set; }
public List<string> UpstreamHttpMethod { get; set; }
public string UpstreamPathTemplate { get; set; }
Expand Down Expand Up @@ -122,7 +133,7 @@ public static void DeepCopy(FileRoute from, FileRoute to)
to.ServiceNamespace = from.ServiceNamespace;
to.Timeout = from.Timeout;
to.UpstreamHeaderTemplates = new Dictionary<string, string>(from.UpstreamHeaderTemplates);
to.UpstreamHeaderTransform = new(from.UpstreamHeaderTransform);
to.UpstreamHeaderTransform = new Dictionary<string, string>(from.UpstreamHeaderTransform);
to.UpstreamHost = from.UpstreamHost;
to.UpstreamHttpMethod = new(from.UpstreamHttpMethod);
to.UpstreamPathTemplate = from.UpstreamPathTemplate;
Expand Down
Loading