Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion src/ReverseProxy/Health/ActiveHealthCheckMonitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ private async Task<DestinationProbingResult> ProbeDestinationAsync(ClusterState
HttpRequestMessage request;
try
{
request = _probingRequestFactory.CreateRequest(cluster.Model, destination.Model);
request = await _probingRequestFactory.CreateRequestAsync(cluster, destination);
}
catch (Exception ex)
{
Expand Down
4 changes: 4 additions & 0 deletions src/ReverseProxy/Health/DefaultProbingRequestFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Net.Http.Headers;
Expand Down Expand Up @@ -39,4 +40,7 @@ public HttpRequestMessage CreateRequest(ClusterModel cluster, DestinationModel d

return request;
}

public ValueTask<HttpRequestMessage> CreateRequestAsync(ClusterState cluster, DestinationState destination) =>
ValueTask.FromResult(CreateRequest(cluster.Model, destination.Model));
}
10 changes: 10 additions & 0 deletions src/ReverseProxy/Health/IProbingRequestFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Net.Http;
using System.Threading.Tasks;
using Yarp.ReverseProxy.Model;

namespace Yarp.ReverseProxy.Health;
Expand All @@ -18,4 +19,13 @@ public interface IProbingRequestFactory
/// <param name="destination">The destination being probed.</param>
/// <returns>Probing <see cref="HttpRequestMessage"/>.</returns>
HttpRequestMessage CreateRequest(ClusterModel cluster, DestinationModel destination);

/// <summary>
/// Creates a probing request.
/// </summary>
/// <param name="cluster">The cluster being probed.</param>
/// <param name="destination">The destination being probed.</param>
/// <returns>Probing <see cref="HttpRequestMessage"/>.</returns>
ValueTask<HttpRequestMessage> CreateRequestAsync(ClusterState cluster, DestinationState destination) =>
ValueTask.FromResult(CreateRequest(cluster.Model, destination.Model));
}
35 changes: 26 additions & 9 deletions test/ReverseProxy.Tests/Health/ActiveHealthCheckMonitorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,37 @@ public async Task CheckHealthAsync_ActiveHealthCheckIsEnabledForCluster_SendProb
VerifySentProbeAndResult(cluster2, httpClient2, policy1, new[] { ("https://localhost:20000/cluster2/api/health/", 1), ("https://localhost:20001/cluster2/api/health/", 1) });
}

[Fact]
public async Task CheckHealthAsync_CustomUserAgentSpecified_UserAgentUnchanged()
[Theory]
[InlineData(false)] // Test old API (CreateRequest with Models) via default implementation
[InlineData(true)] // Test new API (CreateRequestAsync with State)
public async Task CheckHealthAsync_CustomUserAgentSpecified_UserAgentUnchanged(bool overrideAsyncMethod)
{
var policy = new Mock<IActiveHealthCheckPolicy>();
policy.SetupGet(p => p.Name).Returns("policy");

var requestFactory = new Mock<IProbingRequestFactory>();
requestFactory.Setup(p => p.CreateRequest(It.IsAny<ClusterModel>(), It.IsAny<DestinationModel>()))
.Returns((ClusterModel cluster, DestinationModel destination) =>
{
var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:20000/cluster/api/health/");
request.Headers.UserAgent.ParseAdd("FooBar/9001");
return request;
});

HttpRequestMessage CreateCustomRequest()
{
var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:20000/cluster/api/health/");
request.Headers.UserAgent.ParseAdd("FooBar/9001");
return request;
}

if (overrideAsyncMethod)
{
requestFactory.Setup(p => p.CreateRequestAsync(It.IsAny<ClusterState>(), It.IsAny<DestinationState>()))
.Returns(() => ValueTask.FromResult(CreateCustomRequest()));
}
else
{
// Test the old API - the default implementation of CreateRequestAsync should call this
requestFactory.Setup(p => p.CreateRequest(It.IsAny<ClusterModel>(), It.IsAny<DestinationModel>()))
.Returns(CreateCustomRequest);

// Use default interface implementation for CreateRequestAsync
requestFactory.CallBase = true;
}

var options = Options.Create(new ActiveHealthCheckMonitorOptions());
var monitor = new ActiveHealthCheckMonitor(options, new[] { policy.Object }, requestFactory.Object, new Mock<TimeProvider>().Object, GetLogger());
Expand Down