Skip to content

Commit

Permalink
Merge pull request #20 from Lombiq/issue/OSOE-46
Browse files Browse the repository at this point in the history
OSOE-46: Adding reverse proxy service.
  • Loading branch information
sarahelsaig authored Oct 14, 2022
2 parents 643f338 + f597a07 commit 5e38a2e
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 0 deletions.
20 changes: 20 additions & 0 deletions Integration/Services/IProxyConnectionProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Net.Http;

namespace Lombiq.Tests.Integration.Services;

/// <summary>
/// Represents an object that can resolve the required values for the <see cref="TestReverseProxy"/> instance.
/// </summary>
public interface IProxyConnectionProvider
{
/// <summary>
/// Gets the url prefix for where to forward the request to.
/// </summary>
Uri BaseAddress { get; }

/// <summary>
/// Creates the HTTP client used to forward the request to.
/// </summary>
HttpClient CreateClient();
}
136 changes: 136 additions & 0 deletions Integration/Services/TestReverseProxy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Yarp.ReverseProxy.Forwarder;

namespace Lombiq.Tests.Integration.Services;

public class TestReverseProxy : IDisposable, IAsyncDisposable
{
private bool _disposed;
private bool _disposedAsync;
private IWebHost _webHost;
private IProxyConnectionProvider _proxyConnectionProvider;

public string RootUrl { get; private set; }

public TestReverseProxy(string rootUrl) => RootUrl = rootUrl;

public void AttachConnectionProvider(IProxyConnectionProvider clientConnectionProvider) =>
_proxyConnectionProvider = clientConnectionProvider;

public void DetachConnectionProvider() =>
_proxyConnectionProvider = null;

public Task StartAsync()
{
if (_webHost != null)
{
throw new InvalidOperationException("The instance has already started.");
}

var webHostBuilder = new WebHostBuilder()
.UseKestrel()
.UseUrls(RootUrl)
.ConfigureServices(services => services
.AddHttpForwarder()
.AddRouting())
.Configure(builder =>
{
var httpForwarder = builder.ApplicationServices.GetRequiredService<IHttpForwarder>();
builder.UseRouting()
.UseEndpoints(endpoints => endpoints
.Map("/{**catch-all}", async httpContext =>
{
using var client = new HttpMessageInvoker(
new TestProxyMessageHandler(_proxyConnectionProvider.CreateClient()));
await httpForwarder.SendAsync(
httpContext,
_proxyConnectionProvider.BaseAddress.ToString(),
client);
}));
});

_webHost = webHostBuilder.Build();

return _webHost.StartAsync();
}

public Task StopAsync()
{
if (_webHost == null)
{
throw new InvalidOperationException("The instance has not been started.");
}

return StopInternalAsync();
}

private async Task StopInternalAsync()
{
await _webHost.StopAsync();
_webHost.Dispose();
_webHost = null;
}

protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing && !_disposedAsync)
{
DisposeAsync()
.AsTask()
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();
}

_disposed = true;
}
}

public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}

public async ValueTask DisposeAsync()
{
if (_disposed || _disposedAsync) return;

if (_webHost != null) await StopAsync().ConfigureAwait(false);

_disposedAsync = true;

Dispose();
GC.SuppressFinalize(this);
}

// This is required because HttpClient instance is not usable directly because of performance issues. Explained here:
// https://github.com/microsoft/reverse-proxy/blob/92370b140092e852745e98fbc33987da57b723b2/src/ReverseProxy/Forwarder/HttpForwarder.cs#L97
internal class TestProxyMessageHandler : HttpMessageHandler
{
private HttpClient _client;

public TestProxyMessageHandler(HttpClient client) => _client = client;

protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken) => _client.SendAsync(request, cancellationToken);

protected override void Dispose(bool disposing)
{
_client?.Dispose();
_client = null;

base.Dispose(disposing);
}
}
}
1 change: 1 addition & 0 deletions Lombiq.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<PackageReference Include="Shouldly" Version="4.0.3" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="Moq.AutoMock" Version="3.4.0" />
<PackageReference Include="Yarp.ReverseProxy" Version="1.1.1" />
</ItemGroup>

<!-- These are necessary for symbols NuGet packaging, otherwise Shouldly would prevent PDBs to be packaged, see:
Expand Down

0 comments on commit 5e38a2e

Please sign in to comment.