Fix Proxying when StartAdminInterface=true (#778)

* ProxyHelper fixes

* .

* more reformat

* .
This commit is contained in:
Stef Heyenrath
2022-08-09 19:41:45 +02:00
committed by GitHub
parent be4b0addca
commit b1af37f044
39 changed files with 1962 additions and 1907 deletions

View File

@@ -3,45 +3,44 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using WireMock.Types;
namespace WireMock.Owin
namespace WireMock.Owin;
internal partial class AspNetCoreSelfHost
{
internal partial class AspNetCoreSelfHost
public void AddCors(IServiceCollection services)
{
public void AddCors(IServiceCollection services)
if (_wireMockMiddlewareOptions.CorsPolicyOptions > CorsPolicyOptions.None)
{
if (_wireMockMiddlewareOptions.CorsPolicyOptions > CorsPolicyOptions.None)
{
/* https://stackoverflow.com/questions/31942037/how-to-enable-cors-in-asp-net-core */
/* Enable Cors */
services.AddCors(corsOptions => corsOptions
.AddPolicy(CorsPolicyName,
corsPolicyBuilder =>
/* https://stackoverflow.com/questions/31942037/how-to-enable-cors-in-asp-net-core */
/* Enable Cors */
services.AddCors(corsOptions => corsOptions
.AddPolicy(CorsPolicyName,
corsPolicyBuilder =>
{
if (_wireMockMiddlewareOptions.CorsPolicyOptions.Value.HasFlag(CorsPolicyOptions.AllowAnyHeader))
{
if (_wireMockMiddlewareOptions.CorsPolicyOptions.Value.HasFlag(CorsPolicyOptions.AllowAnyHeader))
{
corsPolicyBuilder.AllowAnyHeader();
}
corsPolicyBuilder.AllowAnyHeader();
}
if (_wireMockMiddlewareOptions.CorsPolicyOptions.Value.HasFlag(CorsPolicyOptions.AllowAnyMethod))
{
corsPolicyBuilder.AllowAnyMethod();
}
if (_wireMockMiddlewareOptions.CorsPolicyOptions.Value.HasFlag(CorsPolicyOptions.AllowAnyMethod))
{
corsPolicyBuilder.AllowAnyMethod();
}
if (_wireMockMiddlewareOptions.CorsPolicyOptions.Value.HasFlag(CorsPolicyOptions.AllowAnyOrigin))
{
corsPolicyBuilder.AllowAnyOrigin();
}
}));
}
if (_wireMockMiddlewareOptions.CorsPolicyOptions.Value.HasFlag(CorsPolicyOptions.AllowAnyOrigin))
{
corsPolicyBuilder.AllowAnyOrigin();
}
}));
}
}
public void UseCors(IApplicationBuilder appBuilder)
public void UseCors(IApplicationBuilder appBuilder)
{
if (_wireMockMiddlewareOptions.CorsPolicyOptions > CorsPolicyOptions.None)
{
if (_wireMockMiddlewareOptions.CorsPolicyOptions > CorsPolicyOptions.None)
{
/* Use Cors */
appBuilder.UseCors(CorsPolicyName);
}
/* Use Cors */
appBuilder.UseCors(CorsPolicyName);
}
}
}

View File

@@ -36,10 +36,10 @@ namespace WireMock.Owin
public Exception RunningException => _runningException;
public AspNetCoreSelfHost([NotNull] IWireMockMiddlewareOptions wireMockMiddlewareOptions, [NotNull] HostUrlOptions urlOptions)
public AspNetCoreSelfHost(IWireMockMiddlewareOptions wireMockMiddlewareOptions, HostUrlOptions urlOptions)
{
Guard.NotNull(wireMockMiddlewareOptions, nameof(wireMockMiddlewareOptions));
Guard.NotNull(urlOptions, nameof(urlOptions));
Guard.NotNull(wireMockMiddlewareOptions);
Guard.NotNull(urlOptions);
_logger = wireMockMiddlewareOptions.Logger ?? new WireMockConsoleLogger();
@@ -119,7 +119,7 @@ namespace WireMock.Owin
{
Urls.Add(address.Replace("0.0.0.0", "localhost").Replace("[::]", "localhost"));
PortUtils.TryExtract(address, out bool isHttps, out string protocol, out string host, out int port);
PortUtils.TryExtract(address, out _, out _, out _, out int port);
Ports.Add(port);
}

View File

@@ -1,15 +1,14 @@
namespace WireMock.Owin
namespace WireMock.Owin;
internal struct HostUrlDetails
{
internal class HostUrlDetails
{
public bool IsHttps { get; set; }
public bool IsHttps { get; set; }
public string Url { get; set; }
public string Url { get; set; }
public string Protocol { get; set; }
public string Protocol { get; set; }
public string Host { get; set; }
public string Host { get; set; }
public int Port { get; set; }
}
public int Port { get; set; }
}

View File

@@ -1,46 +1,47 @@
using System.Collections.Generic;
using System.Collections.Generic;
using WireMock.Util;
namespace WireMock.Owin
namespace WireMock.Owin;
internal class HostUrlOptions
{
internal class HostUrlOptions
private const string LOCALHOST = "localhost";
public ICollection<string>? Urls { get; set; }
public int? Port { get; set; }
public bool UseSSL { get; set; }
public ICollection<HostUrlDetails> GetDetails()
{
private const string LOCALHOST = "localhost";
public ICollection<string> Urls { get; set; }
public int? Port { get; set; }
public bool UseSSL { get; set; }
public ICollection<HostUrlDetails> GetDetails()
var list = new List<HostUrlDetails>();
if (Urls == null)
{
var list = new List<HostUrlDetails>();
if (Urls == null)
int port = Port > 0 ? Port.Value : FindFreeTcpPort();
string protocol = UseSSL ? "https" : "http";
list.Add(new HostUrlDetails { IsHttps = UseSSL, Url = $"{protocol}://{LOCALHOST}:{port}", Protocol = protocol, Host = LOCALHOST, Port = port });
}
else
{
foreach (string url in Urls)
{
int port = Port > 0 ? Port.Value : FindFreeTcpPort();
string protocol = UseSSL ? "https" : "http";
list.Add(new HostUrlDetails { IsHttps = UseSSL, Url = $"{protocol}://{LOCALHOST}:{port}", Protocol = protocol, Host = LOCALHOST, Port = port });
}
else
{
foreach (string url in Urls)
if (PortUtils.TryExtract(url, out bool isHttps, out var protocol, out var host, out int port))
{
PortUtils.TryExtract(url, out bool isHttps, out string protocol, out string host, out int port);
list.Add(new HostUrlDetails { IsHttps = isHttps, Url = url, Protocol = protocol, Host = host, Port = port });
}
}
return list;
}
private int FindFreeTcpPort()
{
return list;
}
private static int FindFreeTcpPort()
{
#if USE_ASPNETCORE || NETSTANDARD2_0 || NETSTANDARD2_1
return 0;
return 0;
#else
return PortUtils.FindFreeTcpPort();
return PortUtils.FindFreeTcpPort();
#endif
}
}
}

View File

@@ -1,7 +1,6 @@
namespace WireMock.Owin
namespace WireMock.Owin;
internal interface IMappingMatcher
{
internal interface IMappingMatcher
{
(MappingMatcherResult Match, MappingMatcherResult Partial) FindBestMatch(RequestMessage request);
}
(MappingMatcherResult? Match, MappingMatcherResult? Partial) FindBestMatch(RequestMessage request);
}

View File

@@ -1,36 +1,35 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading.Tasks;
using System;
namespace WireMock.Owin
namespace WireMock.Owin;
interface IOwinSelfHost
{
interface IOwinSelfHost
{
/// <summary>
/// Gets a value indicating whether this server is started.
/// </summary>
/// <value>
/// <c>true</c> if this server is started; otherwise, <c>false</c>.
/// </value>
bool IsStarted { get; }
/// <summary>
/// Gets a value indicating whether this server is started.
/// </summary>
/// <value>
/// <c>true</c> if this server is started; otherwise, <c>false</c>.
/// </value>
bool IsStarted { get; }
/// <summary>
/// Gets the urls.
/// </summary>
List<string> Urls { get; }
/// <summary>
/// Gets the urls.
/// </summary>
List<string> Urls { get; }
/// <summary>
/// Gets the ports.
/// </summary>
List<int> Ports { get; }
/// <summary>
/// Gets the ports.
/// </summary>
List<int> Ports { get; }
/// <summary>
/// The exception occurred when the host is running
/// </summary>
Exception RunningException { get; }
/// <summary>
/// The exception occurred when the host is running.
/// </summary>
Exception? RunningException { get; }
Task StartAsync();
Task StartAsync();
Task StopAsync();
}
Task StopAsync();
}

View File

@@ -27,7 +27,7 @@ namespace WireMock.Owin.Mappers
string method = request.Method;
Dictionary<string, string[]>? headers = null;
var headers = new Dictionary<string, string[]>();
IEnumerable<string>? contentEncodingHeader = null;
if (request.Headers.Any())
{
@@ -43,7 +43,7 @@ namespace WireMock.Owin.Mappers
}
}
IDictionary<string, string>? cookies = null;
var cookies = new Dictionary<string, string>();
if (request.Cookies.Any())
{
cookies = new Dictionary<string, string>();
@@ -75,13 +75,24 @@ namespace WireMock.Owin.Mappers
{
#if !USE_ASPNETCORE
var urlDetails = UrlUtils.Parse(request.Uri, request.PathBase);
string clientIP = request.RemoteIpAddress;
var clientIP = request.RemoteIpAddress;
#else
var urlDetails = UrlUtils.Parse(new Uri(request.GetEncodedUrl()), request.PathBase);
var connection = request.HttpContext.Connection;
string clientIP = connection.RemoteIpAddress.IsIPv4MappedToIPv6
? connection.RemoteIpAddress.MapToIPv4().ToString()
: connection.RemoteIpAddress.ToString();
string clientIP;
if (connection.RemoteIpAddress is null)
{
clientIP = string.Empty;
}
else if (connection.RemoteIpAddress.IsIPv4MappedToIPv6)
{
clientIP = connection.RemoteIpAddress.MapToIPv4().ToString();
}
else
{
clientIP = connection.RemoteIpAddress.ToString();
}
#endif
return (urlDetails, clientIP);
}

View File

@@ -4,72 +4,71 @@ using System.Linq;
using WireMock.Extensions;
using Stef.Validation;
namespace WireMock.Owin
namespace WireMock.Owin;
internal class MappingMatcher : IMappingMatcher
{
internal class MappingMatcher : IMappingMatcher
private readonly IWireMockMiddlewareOptions _options;
public MappingMatcher(IWireMockMiddlewareOptions options)
{
private readonly IWireMockMiddlewareOptions _options;
Guard.NotNull(options, nameof(options));
public MappingMatcher(IWireMockMiddlewareOptions options)
_options = options;
}
public (MappingMatcherResult? Match, MappingMatcherResult? Partial) FindBestMatch(RequestMessage request)
{
var possibleMappings = new List<MappingMatcherResult>();
foreach (var mapping in _options.Mappings.Values.Where(m => m.TimeSettings.IsValid()))
{
Guard.NotNull(options, nameof(options));
_options = options;
}
public (MappingMatcherResult Match, MappingMatcherResult Partial) FindBestMatch(RequestMessage request)
{
var mappings = new List<MappingMatcherResult>();
foreach (var mapping in _options.Mappings.Values.Where(m => m.TimeSettings.IsValid()))
try
{
try
var nextState = GetNextState(mapping);
possibleMappings.Add(new MappingMatcherResult
{
string nextState = GetNextState(mapping);
mappings.Add(new MappingMatcherResult
{
Mapping = mapping,
RequestMatchResult = mapping.GetRequestMatchResult(request, nextState)
});
}
catch (Exception ex)
{
_options.Logger.Error($"Getting a Request MatchResult for Mapping '{mapping.Guid}' failed. This mapping will not be evaluated. Exception: {ex}");
}
Mapping = mapping,
RequestMatchResult = mapping.GetRequestMatchResult(request, nextState)
});
}
var partialMappings = mappings
.Where(pm => (pm.Mapping.IsAdminInterface && pm.RequestMatchResult.IsPerfectMatch) || !pm.Mapping.IsAdminInterface)
.OrderBy(m => m.RequestMatchResult)
.ThenBy(m => m.Mapping.Priority)
.ToList();
var partialMatch = partialMappings.FirstOrDefault(pm => pm.RequestMatchResult.AverageTotalScore > 0.0);
if (_options.AllowPartialMapping == true)
catch (Exception ex)
{
return (partialMatch, partialMatch);
_options.Logger.Error($"Getting a Request MatchResult for Mapping '{mapping.Guid}' failed. This mapping will not be evaluated. Exception: {ex}");
}
var match = mappings
.Where(m => m.RequestMatchResult.IsPerfectMatch)
.OrderBy(m => m.Mapping.Priority).ThenBy(m => m.RequestMatchResult)
.FirstOrDefault();
return (match, partialMatch);
}
private string GetNextState(IMapping mapping)
var partialMappings = possibleMappings
.Where(pm => (pm.Mapping.IsAdminInterface && pm.RequestMatchResult.IsPerfectMatch) || !pm.Mapping.IsAdminInterface)
.OrderBy(m => m.RequestMatchResult)
.ThenBy(m => m.Mapping.Priority)
.ToList();
var partialMatch = partialMappings.FirstOrDefault(pm => pm.RequestMatchResult.AverageTotalScore > 0.0);
if (_options.AllowPartialMapping == true)
{
// If the mapping does not have a scenario or _options.Scenarios does not contain this scenario from the mapping,
// just return null to indicate that there is no next state.
if (mapping.Scenario == null || !_options.Scenarios.ContainsKey(mapping.Scenario))
{
return null;
}
// Else just return the next state
return _options.Scenarios[mapping.Scenario].NextState;
return (partialMatch, partialMatch);
}
var match = possibleMappings
.Where(m => m.RequestMatchResult.IsPerfectMatch)
.OrderBy(m => m.Mapping.Priority).ThenBy(m => m.RequestMatchResult)
.FirstOrDefault();
return (match, partialMatch);
}
private string? GetNextState(IMapping mapping)
{
// If the mapping does not have a scenario or _options.Scenarios does not contain this scenario from the mapping,
// just return null to indicate that there is no next state.
if (mapping.Scenario == null || !_options.Scenarios.ContainsKey(mapping.Scenario))
{
return null;
}
// Else just return the next state
return _options.Scenarios[mapping.Scenario].NextState;
}
}

View File

@@ -1,11 +1,10 @@
using WireMock.Matchers.Request;
namespace WireMock.Owin
{
internal class MappingMatcherResult
{
public IMapping Mapping { get; set; }
namespace WireMock.Owin;
public IRequestMatchResult RequestMatchResult { get; set; }
}
internal class MappingMatcherResult
{
public IMapping Mapping { get; set; }
public IRequestMatchResult RequestMatchResult { get; set; }
}

View File

@@ -1,110 +1,109 @@
#if !USE_ASPNETCORE
using JetBrains.Annotations;
using Microsoft.Owin.Hosting;
using Owin;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using WireMock.Logging;
using WireMock.Owin.Mappers;
using Stef.Validation;
namespace WireMock.Owin
namespace WireMock.Owin;
internal class OwinSelfHost : IOwinSelfHost
{
internal class OwinSelfHost : IOwinSelfHost
private readonly IWireMockMiddlewareOptions _options;
private readonly CancellationTokenSource _cts = new();
private readonly IWireMockLogger _logger;
private Exception? _runningException;
public OwinSelfHost(IWireMockMiddlewareOptions options, HostUrlOptions urlOptions)
{
private readonly IWireMockMiddlewareOptions _options;
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
private readonly IWireMockLogger _logger;
Guard.NotNull(options, nameof(options));
Guard.NotNull(urlOptions, nameof(urlOptions));
private Exception _runningException;
_options = options;
_logger = options.Logger ?? new WireMockConsoleLogger();
public OwinSelfHost([NotNull] IWireMockMiddlewareOptions options, [NotNull] HostUrlOptions urlOptions)
foreach (var detail in urlOptions.GetDetails())
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(urlOptions, nameof(urlOptions));
_options = options;
_logger = options.Logger ?? new WireMockConsoleLogger();
foreach (var detail in urlOptions.GetDetails())
{
Urls.Add(detail.Url);
Ports.Add(detail.Port);
}
Urls.Add(detail.Url);
Ports.Add(detail.Port);
}
}
public bool IsStarted { get; private set; }
public bool IsStarted { get; private set; }
public List<string> Urls { get; } = new List<string>();
public List<string> Urls { get; } = new();
public List<int> Ports { get; } = new List<int>();
public List<int> Ports { get; } = new();
public Exception RunningException => _runningException;
public Exception? RunningException => _runningException;
[PublicAPI]
public Task StartAsync()
{
return Task.Run(StartServers, _cts.Token);
}
[PublicAPI]
public Task StartAsync()
{
return Task.Run(StartServers, _cts.Token);
}
[PublicAPI]
public Task StopAsync()
{
_cts.Cancel();
[PublicAPI]
public Task StopAsync()
{
_cts.Cancel();
return Task.FromResult(true);
}
return Task.FromResult(true);
}
private void StartServers()
{
private void StartServers()
{
#if NET46
_logger.Info("Server using .net 4.6.1 or higher");
_logger.Info("Server using .net 4.6.1 or higher");
#else
_logger.Info("Server using .net 4.5.x");
_logger.Info("Server using .net 4.5.x");
#endif
var servers = new List<IDisposable>();
var servers = new List<IDisposable>();
try
try
{
var requestMapper = new OwinRequestMapper();
var responseMapper = new OwinResponseMapper(_options);
var matcher = new MappingMatcher(_options);
Action<IAppBuilder> startup = app =>
{
var requestMapper = new OwinRequestMapper();
var responseMapper = new OwinResponseMapper(_options);
var matcher = new MappingMatcher(_options);
app.Use<GlobalExceptionMiddleware>(_options, responseMapper);
_options.PreWireMockMiddlewareInit?.Invoke(app);
app.Use<WireMockMiddleware>(_options, requestMapper, responseMapper, matcher);
_options.PostWireMockMiddlewareInit?.Invoke(app);
};
Action<IAppBuilder> startup = app =>
{
app.Use<GlobalExceptionMiddleware>(_options, responseMapper);
_options.PreWireMockMiddlewareInit?.Invoke(app);
app.Use<WireMockMiddleware>(_options, requestMapper, responseMapper, matcher);
_options.PostWireMockMiddlewareInit?.Invoke(app);
};
foreach (var url in Urls)
{
servers.Add(WebApp.Start(url, startup));
}
IsStarted = true;
// WaitHandle is signaled when the token is cancelled,
// which will be more efficient than Thread.Sleep in while loop
_cts.Token.WaitHandle.WaitOne();
}
catch (Exception e)
foreach (var url in Urls)
{
// Expose exception of starting host, otherwise it's hard to be troubleshooting if keeping quiet
// For example, WebApp.Start will fail with System.MissingMemberException if Microsoft.Owin.Host.HttpListener.dll is being located
// https://stackoverflow.com/questions/25090211/owin-httplistener-not-located/31369857
_runningException = e;
_logger.Error(e.ToString());
}
finally
{
IsStarted = false;
// Dispose all servers in finally block to make sure clean up allocated resource on error happening
servers.ForEach(s => s.Dispose());
servers.Add(WebApp.Start(url, startup));
}
IsStarted = true;
// WaitHandle is signaled when the token is cancelled,
// which will be more efficient than Thread.Sleep in while loop
_cts.Token.WaitHandle.WaitOne();
}
catch (Exception e)
{
// Expose exception of starting host, otherwise it's hard to be troubleshooting if keeping quiet
// For example, WebApp.Start will fail with System.MissingMemberException if Microsoft.Owin.Host.HttpListener.dll is being located
// https://stackoverflow.com/questions/25090211/owin-httplistener-not-located/31369857
_runningException = e;
_logger.Error(e.ToString());
}
finally
{
IsStarted = false;
// Dispose all servers in finally block to make sure clean up allocated resource on error happening
servers.ForEach(s => s.Dispose());
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Threading.Tasks;
using System.Linq;
using System.Net;
using Stef.Validation;
using WireMock.Logging;
using WireMock.Matchers;
@@ -74,10 +75,16 @@ namespace WireMock.Owin
var logRequest = false;
IResponseMessage? response = null;
(MappingMatcherResult? Match, MappingMatcherResult? Partial) result = (null, null);
try
{
foreach (var mapping in _options.Mappings.Values.Where(m => m?.Scenario != null))
foreach (var mapping in _options.Mappings.Values)
{
if (mapping.Scenario is null)
{
continue;
}
// Set scenario start
if (!_options.Scenarios.ContainsKey(mapping.Scenario) && mapping.IsStartState)
{
@@ -107,7 +114,7 @@ namespace WireMock.Owin
if (!present || _options.AuthenticationMatcher.IsMatch(authorization.ToString()) < MatchScores.Perfect)
{
_options.Logger.Error("HttpStatusCode set to 401");
response = ResponseMessageBuilder.Create(null, 401);
response = ResponseMessageBuilder.Create(null, HttpStatusCode.Unauthorized);
return;
}
}
@@ -194,7 +201,7 @@ namespace WireMock.Owin
private async Task SendToWebhooksAsync(IMapping mapping, IRequestMessage request, IResponseMessage response)
{
for (int index = 0; index < mapping.Webhooks.Length; index++)
for (int index = 0; index < mapping.Webhooks?.Length; index++)
{
var httpClientForWebhook = HttpClientBuilder.Build(mapping.Settings.WebhookSettings ?? new WebhookSettings());
var webhookSender = new WebhookSender(mapping.Settings);
@@ -212,7 +219,7 @@ namespace WireMock.Owin
private void UpdateScenarioState(IMapping mapping)
{
var scenario = _options.Scenarios[mapping.Scenario];
var scenario = _options.Scenarios[mapping.Scenario!];
// Increase the number of times this state has been executed
scenario.Counter++;