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

@@ -7,6 +7,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SSL/@EntryIndexedValue">SSL</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SSL/@EntryIndexedValue">SSL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=TE/@EntryIndexedValue">TE</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=TE/@EntryIndexedValue">TE</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=TSV/@EntryIndexedValue">TSV</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=TSV/@EntryIndexedValue">TSV</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=TTL/@EntryIndexedValue">TTL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=WWW/@EntryIndexedValue">WWW</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=WWW/@EntryIndexedValue">WWW</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=XMS/@EntryIndexedValue">XMS</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=XMS/@EntryIndexedValue">XMS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=XUA/@EntryIndexedValue">XUA</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=XUA/@EntryIndexedValue">XUA</s:String>

View File

@@ -30,12 +30,12 @@ public class LogEntryModel
/// <summary> /// <summary>
/// The mapping unique title. /// The mapping unique title.
/// </summary> /// </summary>
public string MappingTitle { get; set; } public string? MappingTitle { get; set; }
/// <summary> /// <summary>
/// The request match result. /// The request match result.
/// </summary> /// </summary>
public LogRequestMatchModel RequestMatchResult { get; set; } public LogRequestMatchModel? RequestMatchResult { get; set; }
/// <summary> /// <summary>
/// The partial mapping unique identifier. /// The partial mapping unique identifier.
@@ -45,10 +45,10 @@ public class LogEntryModel
/// <summary> /// <summary>
/// The partial mapping unique title. /// The partial mapping unique title.
/// </summary> /// </summary>
public string PartialMappingTitle { get; set; } public string? PartialMappingTitle { get; set; }
/// <summary> /// <summary>
/// The partial request match result. /// The partial request match result.
/// </summary> /// </summary>
public LogRequestMatchModel PartialRequestMatchResult { get; set; } public LogRequestMatchModel? PartialRequestMatchResult { get; set; }
} }

View File

@@ -63,12 +63,12 @@ public interface IRequestMessage
/// <summary> /// <summary>
/// Gets the headers. /// Gets the headers.
/// </summary> /// </summary>
IDictionary<string, WireMockList<string>>? Headers { get; } IDictionary<string, WireMockList<string>> Headers { get; }
/// <summary> /// <summary>
/// Gets the cookies. /// Gets the cookies.
/// </summary> /// </summary>
IDictionary<string, string>? Cookies { get; } IDictionary<string, string> Cookies { get; }
/// <summary> /// <summary>
/// Gets the query. /// Gets the query.

View File

@@ -1,25 +1,24 @@
using System; using System;
namespace WireMock.Models namespace WireMock.Models;
/// <summary>
/// TimeSettings: Start, End and TTL
/// </summary>
public interface ITimeSettings
{ {
/// <summary> /// <summary>
/// TimeSettings: Start, End and TTL /// Gets or sets the DateTime from which this mapping should be used. In case this is not defined, it's used (default behavior).
/// </summary> /// </summary>
public interface ITimeSettings DateTime? Start { get; set; }
{
/// <summary>
/// Gets or sets the DateTime from which this mapping should be used. In case this is not defined, it's used (default behavior).
/// </summary>
DateTime? Start { get; set; }
/// <summary> /// <summary>
/// Gets or sets the DateTime from until this mapping should be used. In case this is not defined, it's used forever (default behavior). /// Gets or sets the DateTime from until this mapping should be used. In case this is not defined, it's used forever (default behavior).
/// </summary> /// </summary>
DateTime? End { get; set; } DateTime? End { get; set; }
/// <summary> /// <summary>
/// Gets or sets the TTL (Time To Live) in seconds for this mapping. In case this is not defined, it's used (default behavior). /// Gets or sets the TTL (Time To Live) in seconds for this mapping. In case this is not defined, it's used (default behavior).
/// </summary> /// </summary>
int? TTL { get; set; } int? TTL { get; set; }
}
} }

View File

@@ -1,35 +1,34 @@
using System; using System;
using JetBrains.Annotations;
using WireMock.Models; using WireMock.Models;
namespace WireMock.Extensions namespace WireMock.Extensions;
internal static class TimeSettingsExtensions
{ {
internal static class TimeSettingsExtensions public static bool IsValid(this ITimeSettings? settings)
{ {
public static bool IsValid([CanBeNull] this ITimeSettings settings) if (settings == null)
{ {
if (settings == null) return true;
{
return true;
}
var now = DateTime.Now;
var start = settings.Start != null ? settings.Start.Value : now;
DateTime end;
if (settings.End != null)
{
end = settings.End.Value;
}
else if (settings.TTL != null)
{
end = start.AddSeconds(settings.TTL.Value);
}
else
{
end = DateTime.MaxValue;
}
return now >= start && now <= end;
} }
var now = DateTime.Now;
var start = settings.Start ?? now;
DateTime end;
if (settings.End != null)
{
end = settings.End.Value;
}
else if (settings.TTL != null)
{
end = start.AddSeconds(settings.TTL.Value);
}
else
{
end = DateTime.MaxValue;
}
return now >= start && now <= end;
} }
} }

View File

@@ -20,22 +20,22 @@ public interface IMapping
/// <summary> /// <summary>
/// Gets the TimeSettings (Start, End and TTL). /// Gets the TimeSettings (Start, End and TTL).
/// </summary> /// </summary>
ITimeSettings TimeSettings { get; } ITimeSettings? TimeSettings { get; }
/// <summary> /// <summary>
/// Gets the unique title. /// Gets the unique title.
/// </summary> /// </summary>
string Title { get; } string? Title { get; }
/// <summary> /// <summary>
/// Gets the description. /// Gets the description.
/// </summary> /// </summary>
string Description { get; } string? Description { get; }
/// <summary> /// <summary>
/// The full filename path for this mapping (only defined for static mappings). /// The full filename path for this mapping (only defined for static mappings).
/// </summary> /// </summary>
string Path { get; set; } string? Path { get; set; }
/// <summary> /// <summary>
/// Gets the priority. (A low value means higher priority.) /// Gets the priority. (A low value means higher priority.)

View File

@@ -1,149 +1,147 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations;
using WireMock.Matchers.Request; using WireMock.Matchers.Request;
using WireMock.Models; using WireMock.Models;
using WireMock.ResponseProviders; using WireMock.ResponseProviders;
using WireMock.Settings; using WireMock.Settings;
namespace WireMock namespace WireMock;
/// <summary>
/// The Mapping.
/// </summary>
public class Mapping : IMapping
{ {
/// <inheritdoc />
public Guid Guid { get; }
/// <inheritdoc />
public string? Title { get; }
/// <inheritdoc />
public string? Description { get; }
/// <inheritdoc />
public string? Path { get; set; }
/// <inheritdoc />
public int Priority { get; }
/// <inheritdoc />
public string? Scenario { get; }
/// <inheritdoc />
public string? ExecutionConditionState { get; }
/// <inheritdoc />
public string? NextState { get; }
/// <inheritdoc />
public int? StateTimes { get; }
/// <inheritdoc />
public IRequestMatcher RequestMatcher { get; }
/// <inheritdoc />
public IResponseProvider Provider { get; }
/// <inheritdoc />
public WireMockServerSettings Settings { get; }
/// <inheritdoc />
public bool IsStartState => Scenario == null || Scenario != null && NextState != null && ExecutionConditionState == null;
/// <inheritdoc />
public bool IsAdminInterface => Provider is DynamicResponseProvider or DynamicAsyncResponseProvider or ProxyAsyncResponseProvider;
/// <inheritdoc />
public bool IsProxy => Provider is ProxyAsyncResponseProvider;
/// <inheritdoc />
public bool LogMapping => Provider is not (DynamicResponseProvider or DynamicAsyncResponseProvider);
/// <inheritdoc />
public IWebhook[]? Webhooks { get; }
/// <inheritdoc />
public ITimeSettings? TimeSettings { get; }
/// <summary> /// <summary>
/// The Mapping. /// Initializes a new instance of the <see cref="Mapping"/> class.
/// </summary> /// </summary>
public class Mapping : IMapping /// <param name="guid">The unique identifier.</param>
/// <param name="title">The unique title (can be null).</param>
/// <param name="description">The description (can be null).</param>
/// <param name="path">The full file path from this mapping title (can be null).</param>
/// <param name="settings">The WireMockServerSettings.</param>
/// <param name="requestMatcher">The request matcher.</param>
/// <param name="provider">The provider.</param>
/// <param name="priority">The priority for this mapping.</param>
/// <param name="scenario">The scenario. [Optional]</param>
/// <param name="executionConditionState">State in which the current mapping can occur. [Optional]</param>
/// <param name="nextState">The next state which will occur after the current mapping execution. [Optional]</param>
/// <param name="stateTimes">Only when the current state is executed this number, the next state which will occur. [Optional]</param>
/// <param name="webhooks">The Webhooks. [Optional]</param>
/// <param name="timeSettings">The TimeSettings. [Optional]</param>
public Mapping(
Guid guid,
string? title,
string? description,
string? path,
WireMockServerSettings settings,
IRequestMatcher requestMatcher,
IResponseProvider provider,
int priority,
string? scenario,
string? executionConditionState,
string? nextState,
int? stateTimes,
IWebhook[]? webhooks,
ITimeSettings? timeSettings)
{ {
/// <inheritdoc /> Guid = guid;
public Guid Guid { get; } Title = title;
Description = description;
Path = path;
Settings = settings;
RequestMatcher = requestMatcher;
Provider = provider;
Priority = priority;
Scenario = scenario;
ExecutionConditionState = executionConditionState;
NextState = nextState;
StateTimes = stateTimes;
Webhooks = webhooks;
TimeSettings = timeSettings;
}
/// <inheritdoc /> /// <inheritdoc cref="IMapping.ProvideResponseAsync" />
public string Title { get; } public Task<(IResponseMessage Message, IMapping Mapping)> ProvideResponseAsync(IRequestMessage requestMessage)
{
return Provider.ProvideResponseAsync(requestMessage, Settings);
}
/// <inheritdoc /> /// <inheritdoc cref="IMapping.GetRequestMatchResult" />
public string Description { get; } public IRequestMatchResult GetRequestMatchResult(IRequestMessage requestMessage, string? nextState)
{
var result = new RequestMatchResult();
/// <inheritdoc /> RequestMatcher.GetMatchingScore(requestMessage, result);
public string Path { get; set; }
/// <inheritdoc /> // Only check state if Scenario is defined
public int Priority { get; } if (Scenario != null)
/// <inheritdoc />
public string Scenario { get; }
/// <inheritdoc />
public string ExecutionConditionState { get; }
/// <inheritdoc />
public string NextState { get; }
/// <inheritdoc />
public int? StateTimes { get; }
/// <inheritdoc />
public IRequestMatcher RequestMatcher { get; }
/// <inheritdoc />
public IResponseProvider Provider { get; }
/// <inheritdoc />
public WireMockServerSettings Settings { get; }
/// <inheritdoc />
public bool IsStartState => Scenario == null || Scenario != null && NextState != null && ExecutionConditionState == null;
/// <inheritdoc />
public bool IsAdminInterface => Provider is DynamicResponseProvider || Provider is DynamicAsyncResponseProvider || Provider is ProxyAsyncResponseProvider;
/// <inheritdoc />
public bool IsProxy => Provider is ProxyAsyncResponseProvider;
/// <inheritdoc />
public bool LogMapping => !(Provider is DynamicResponseProvider || Provider is DynamicAsyncResponseProvider);
/// <inheritdoc />
public IWebhook[] Webhooks { get; }
/// <inheritdoc />
public ITimeSettings TimeSettings { get; }
/// <summary>
/// Initializes a new instance of the <see cref="Mapping"/> class.
/// </summary>
/// <param name="guid">The unique identifier.</param>
/// <param name="title">The unique title (can be null).</param>
/// <param name="description">The description (can be null).</param>
/// <param name="path">The full file path from this mapping title (can be null).</param>
/// <param name="settings">The WireMockServerSettings.</param>
/// <param name="requestMatcher">The request matcher.</param>
/// <param name="provider">The provider.</param>
/// <param name="priority">The priority for this mapping.</param>
/// <param name="scenario">The scenario. [Optional]</param>
/// <param name="executionConditionState">State in which the current mapping can occur. [Optional]</param>
/// <param name="nextState">The next state which will occur after the current mapping execution. [Optional]</param>
/// <param name="stateTimes">Only when the current state is executed this number, the next state which will occur. [Optional]</param>
/// <param name="webhooks">The Webhooks. [Optional]</param>
/// <param name="timeSettings">The TimeSettings. [Optional]</param>
public Mapping(
Guid guid,
string? title,
string? description,
string? path,
WireMockServerSettings settings,
IRequestMatcher requestMatcher,
IResponseProvider provider,
int priority,
string? scenario,
string? executionConditionState,
string? nextState,
int? stateTimes,
IWebhook[]? webhooks,
ITimeSettings? timeSettings)
{ {
Guid = guid; var matcher = new RequestMessageScenarioAndStateMatcher(nextState, ExecutionConditionState);
Title = title; matcher.GetMatchingScore(requestMessage, result);
Description = description; //// If ExecutionConditionState is null, this means that request is the start from a scenario. So just return.
Path = path; //if (ExecutionConditionState != null)
Settings = settings; //{
RequestMatcher = requestMatcher; // // ExecutionConditionState is not null, so get score for matching with the nextState.
Provider = provider; // var matcher = new RequestMessageScenarioAndStateMatcher(nextState, ExecutionConditionState);
Priority = priority; // matcher.GetMatchingScore(requestMessage, result);
Scenario = scenario; //}
ExecutionConditionState = executionConditionState;
NextState = nextState;
StateTimes = stateTimes;
Webhooks = webhooks;
TimeSettings = timeSettings;
} }
/// <inheritdoc cref="IMapping.ProvideResponseAsync" /> return result;
public Task<(IResponseMessage Message, IMapping Mapping)> ProvideResponseAsync(IRequestMessage requestMessage)
{
return Provider.ProvideResponseAsync(requestMessage, Settings);
}
/// <inheritdoc cref="IMapping.GetRequestMatchResult" />
public IRequestMatchResult GetRequestMatchResult(IRequestMessage requestMessage, string nextState)
{
var result = new RequestMatchResult();
RequestMatcher.GetMatchingScore(requestMessage, result);
// Only check state if Scenario is defined
if (Scenario != null)
{
var matcher = new RequestMessageScenarioAndStateMatcher(nextState, ExecutionConditionState);
matcher.GetMatchingScore(requestMessage, result);
//// If ExecutionConditionState is null, this means that request is the start from a scenario. So just return.
//if (ExecutionConditionState != null)
//{
// // ExecutionConditionState is not null, so get score for matching with the nextState.
// var matcher = new RequestMessageScenarioAndStateMatcher(nextState, ExecutionConditionState);
// matcher.GetMatchingScore(requestMessage, result);
//}
}
return result;
}
} }
} }

View File

@@ -1,46 +1,41 @@
using JetBrains.Annotations; namespace WireMock.Matchers.Request;
namespace WireMock.Matchers.Request /// <summary>
/// The scenario and state matcher.
/// </summary>
internal class RequestMessageScenarioAndStateMatcher : IRequestMatcher
{ {
/// <summary> /// <summary>
/// The scenario and state matcher. /// Execution state condition for the current mapping.
/// </summary> /// </summary>
internal class RequestMessageScenarioAndStateMatcher : IRequestMatcher private readonly string? _executionConditionState;
/// <summary>
/// The next state which will be signaled after the current mapping execution.
/// In case the value is null state will not be changed.
/// </summary>
private readonly string? _nextState;
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageScenarioAndStateMatcher"/> class.
/// </summary>
/// <param name="nextState">The next state.</param>
/// <param name="executionConditionState">Execution state condition for the current mapping.</param>
public RequestMessageScenarioAndStateMatcher(string? nextState, string? executionConditionState)
{ {
/// <summary> _nextState = nextState;
/// Execution state condition for the current mapping. _executionConditionState = executionConditionState;
/// </summary> }
[CanBeNull]
private readonly string _executionConditionState;
/// <summary> /// <inheritdoc />
/// The next state which will be signaled after the current mapping execution. public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
/// In case the value is null state will not be changed. {
/// </summary> double score = IsMatch();
[CanBeNull] return requestMatchResult.AddScore(GetType(), score);
private readonly string _nextState; }
/// <summary> private double IsMatch()
/// Initializes a new instance of the <see cref="RequestMessageScenarioAndStateMatcher"/> class. {
/// </summary> return Equals(_executionConditionState, _nextState) ? MatchScores.Perfect : MatchScores.Mismatch;
/// <param name="nextState">The next state.</param>
/// <param name="executionConditionState">Execution state condition for the current mapping.</param>
public RequestMessageScenarioAndStateMatcher([CanBeNull] string nextState, [CanBeNull] string executionConditionState)
{
_nextState = nextState;
_executionConditionState = executionConditionState;
}
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
double score = IsMatch();
return requestMatchResult.AddScore(GetType(), score);
}
private double IsMatch()
{
return Equals(_executionConditionState, _nextState) ? MatchScores.Perfect : MatchScores.Mismatch;
}
} }
} }

View File

@@ -3,45 +3,44 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using WireMock.Types; 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 */
/* https://stackoverflow.com/questions/31942037/how-to-enable-cors-in-asp-net-core */ services.AddCors(corsOptions => corsOptions
/* Enable Cors */ .AddPolicy(CorsPolicyName,
services.AddCors(corsOptions => corsOptions corsPolicyBuilder =>
.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)) if (_wireMockMiddlewareOptions.CorsPolicyOptions.Value.HasFlag(CorsPolicyOptions.AllowAnyMethod))
{ {
corsPolicyBuilder.AllowAnyMethod(); corsPolicyBuilder.AllowAnyMethod();
} }
if (_wireMockMiddlewareOptions.CorsPolicyOptions.Value.HasFlag(CorsPolicyOptions.AllowAnyOrigin)) if (_wireMockMiddlewareOptions.CorsPolicyOptions.Value.HasFlag(CorsPolicyOptions.AllowAnyOrigin))
{ {
corsPolicyBuilder.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 Exception RunningException => _runningException;
public AspNetCoreSelfHost([NotNull] IWireMockMiddlewareOptions wireMockMiddlewareOptions, [NotNull] HostUrlOptions urlOptions) public AspNetCoreSelfHost(IWireMockMiddlewareOptions wireMockMiddlewareOptions, HostUrlOptions urlOptions)
{ {
Guard.NotNull(wireMockMiddlewareOptions, nameof(wireMockMiddlewareOptions)); Guard.NotNull(wireMockMiddlewareOptions);
Guard.NotNull(urlOptions, nameof(urlOptions)); Guard.NotNull(urlOptions);
_logger = wireMockMiddlewareOptions.Logger ?? new WireMockConsoleLogger(); _logger = wireMockMiddlewareOptions.Logger ?? new WireMockConsoleLogger();
@@ -119,7 +119,7 @@ namespace WireMock.Owin
{ {
Urls.Add(address.Replace("0.0.0.0", "localhost").Replace("[::]", "localhost")); 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); 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; 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"; var list = new List<HostUrlDetails>();
if (Urls == null)
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>(); int port = Port > 0 ? Port.Value : FindFreeTcpPort();
if (Urls == null) 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(); if (PortUtils.TryExtract(url, out bool isHttps, out var protocol, out var host, out int port))
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)
{ {
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 }); 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 #if USE_ASPNETCORE || NETSTANDARD2_0 || NETSTANDARD2_1
return 0; return 0;
#else #else
return PortUtils.FindFreeTcpPort(); return PortUtils.FindFreeTcpPort();
#endif #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.Threading.Tasks;
using System; using System;
namespace WireMock.Owin namespace WireMock.Owin;
interface IOwinSelfHost
{ {
interface IOwinSelfHost /// <summary>
{ /// Gets a value indicating whether this server is started.
/// <summary> /// </summary>
/// Gets a value indicating whether this server is started. /// <value>
/// </summary> /// <c>true</c> if this server is started; otherwise, <c>false</c>.
/// <value> /// </value>
/// <c>true</c> if this server is started; otherwise, <c>false</c>. bool IsStarted { get; }
/// </value>
bool IsStarted { get; }
/// <summary> /// <summary>
/// Gets the urls. /// Gets the urls.
/// </summary> /// </summary>
List<string> Urls { get; } List<string> Urls { get; }
/// <summary> /// <summary>
/// Gets the ports. /// Gets the ports.
/// </summary> /// </summary>
List<int> Ports { get; } List<int> Ports { get; }
/// <summary> /// <summary>
/// The exception occurred when the host is running /// The exception occurred when the host is running.
/// </summary> /// </summary>
Exception RunningException { get; } 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; string method = request.Method;
Dictionary<string, string[]>? headers = null; var headers = new Dictionary<string, string[]>();
IEnumerable<string>? contentEncodingHeader = null; IEnumerable<string>? contentEncodingHeader = null;
if (request.Headers.Any()) 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()) if (request.Cookies.Any())
{ {
cookies = new Dictionary<string, string>(); cookies = new Dictionary<string, string>();
@@ -75,13 +75,24 @@ namespace WireMock.Owin.Mappers
{ {
#if !USE_ASPNETCORE #if !USE_ASPNETCORE
var urlDetails = UrlUtils.Parse(request.Uri, request.PathBase); var urlDetails = UrlUtils.Parse(request.Uri, request.PathBase);
string clientIP = request.RemoteIpAddress; var clientIP = request.RemoteIpAddress;
#else #else
var urlDetails = UrlUtils.Parse(new Uri(request.GetEncodedUrl()), request.PathBase); var urlDetails = UrlUtils.Parse(new Uri(request.GetEncodedUrl()), request.PathBase);
var connection = request.HttpContext.Connection; var connection = request.HttpContext.Connection;
string clientIP = connection.RemoteIpAddress.IsIPv4MappedToIPv6 string clientIP;
? connection.RemoteIpAddress.MapToIPv4().ToString() if (connection.RemoteIpAddress is null)
: connection.RemoteIpAddress.ToString(); {
clientIP = string.Empty;
}
else if (connection.RemoteIpAddress.IsIPv4MappedToIPv6)
{
clientIP = connection.RemoteIpAddress.MapToIPv4().ToString();
}
else
{
clientIP = connection.RemoteIpAddress.ToString();
}
#endif #endif
return (urlDetails, clientIP); return (urlDetails, clientIP);
} }

View File

@@ -4,72 +4,71 @@ using System.Linq;
using WireMock.Extensions; using WireMock.Extensions;
using Stef.Validation; 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)); try
_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 var nextState = GetNextState(mapping);
possibleMappings.Add(new MappingMatcherResult
{ {
string nextState = GetNextState(mapping); Mapping = mapping,
RequestMatchResult = mapping.GetRequestMatchResult(request, nextState)
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}");
}
} }
catch (Exception ex)
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)
{ {
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, return (partialMatch, partialMatch);
// 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;
} }
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; using WireMock.Matchers.Request;
namespace WireMock.Owin namespace WireMock.Owin;
{
internal class MappingMatcherResult
{
public IMapping Mapping { get; set; }
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 #if !USE_ASPNETCORE
using JetBrains.Annotations;
using Microsoft.Owin.Hosting; using Microsoft.Owin.Hosting;
using Owin; using Owin;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations;
using WireMock.Logging; using WireMock.Logging;
using WireMock.Owin.Mappers; using WireMock.Owin.Mappers;
using Stef.Validation; 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; Guard.NotNull(options, nameof(options));
private readonly CancellationTokenSource _cts = new CancellationTokenSource(); Guard.NotNull(urlOptions, nameof(urlOptions));
private readonly IWireMockLogger _logger;
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)); Urls.Add(detail.Url);
Guard.NotNull(urlOptions, nameof(urlOptions)); Ports.Add(detail.Port);
_options = options;
_logger = options.Logger ?? new WireMockConsoleLogger();
foreach (var detail in urlOptions.GetDetails())
{
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] [PublicAPI]
public Task StartAsync() public Task StartAsync()
{ {
return Task.Run(StartServers, _cts.Token); return Task.Run(StartServers, _cts.Token);
} }
[PublicAPI] [PublicAPI]
public Task StopAsync() public Task StopAsync()
{ {
_cts.Cancel(); _cts.Cancel();
return Task.FromResult(true); return Task.FromResult(true);
} }
private void StartServers() private void StartServers()
{ {
#if NET46 #if NET46
_logger.Info("Server using .net 4.6.1 or higher"); _logger.Info("Server using .net 4.6.1 or higher");
#else #else
_logger.Info("Server using .net 4.5.x"); _logger.Info("Server using .net 4.5.x");
#endif #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(); app.Use<GlobalExceptionMiddleware>(_options, responseMapper);
var responseMapper = new OwinResponseMapper(_options); _options.PreWireMockMiddlewareInit?.Invoke(app);
var matcher = new MappingMatcher(_options); app.Use<WireMockMiddleware>(_options, requestMapper, responseMapper, matcher);
_options.PostWireMockMiddlewareInit?.Invoke(app);
};
Action<IAppBuilder> startup = app => foreach (var url in Urls)
{
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)
{ {
// Expose exception of starting host, otherwise it's hard to be troubleshooting if keeping quiet servers.Add(WebApp.Start(url, startup));
// 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());
} }
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;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Linq; using System.Linq;
using System.Net;
using Stef.Validation; using Stef.Validation;
using WireMock.Logging; using WireMock.Logging;
using WireMock.Matchers; using WireMock.Matchers;
@@ -74,10 +75,16 @@ namespace WireMock.Owin
var logRequest = false; var logRequest = false;
IResponseMessage? response = null; IResponseMessage? response = null;
(MappingMatcherResult? Match, MappingMatcherResult? Partial) result = (null, null); (MappingMatcherResult? Match, MappingMatcherResult? Partial) result = (null, null);
try 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 // Set scenario start
if (!_options.Scenarios.ContainsKey(mapping.Scenario) && mapping.IsStartState) if (!_options.Scenarios.ContainsKey(mapping.Scenario) && mapping.IsStartState)
{ {
@@ -107,7 +114,7 @@ namespace WireMock.Owin
if (!present || _options.AuthenticationMatcher.IsMatch(authorization.ToString()) < MatchScores.Perfect) if (!present || _options.AuthenticationMatcher.IsMatch(authorization.ToString()) < MatchScores.Perfect)
{ {
_options.Logger.Error("HttpStatusCode set to 401"); _options.Logger.Error("HttpStatusCode set to 401");
response = ResponseMessageBuilder.Create(null, 401); response = ResponseMessageBuilder.Create(null, HttpStatusCode.Unauthorized);
return; return;
} }
} }
@@ -194,7 +201,7 @@ namespace WireMock.Owin
private async Task SendToWebhooksAsync(IMapping mapping, IRequestMessage request, IResponseMessage response) 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 httpClientForWebhook = HttpClientBuilder.Build(mapping.Settings.WebhookSettings ?? new WebhookSettings());
var webhookSender = new WebhookSender(mapping.Settings); var webhookSender = new WebhookSender(mapping.Settings);
@@ -212,7 +219,7 @@ namespace WireMock.Owin
private void UpdateScenarioState(IMapping mapping) 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 // Increase the number of times this state has been executed
scenario.Counter++; scenario.Counter++;

View File

@@ -4,6 +4,7 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Stef.Validation; using Stef.Validation;
using WireMock.Constants;
using WireMock.Http; using WireMock.Http;
using WireMock.Matchers; using WireMock.Matchers;
using WireMock.RequestBuilders; using WireMock.RequestBuilders;
@@ -29,9 +30,9 @@ internal class ProxyHelper
IRequestMessage requestMessage, IRequestMessage requestMessage,
string url) string url)
{ {
Guard.NotNull(client, nameof(client)); Guard.NotNull(client);
Guard.NotNull(requestMessage, nameof(requestMessage)); Guard.NotNull(requestMessage);
Guard.NotNull(url, nameof(url)); Guard.NotNull(url);
var originalUri = new Uri(requestMessage.Url); var originalUri = new Uri(requestMessage.Url);
var requiredUri = new Uri(url); var requiredUri = new Uri(url);
@@ -103,6 +104,22 @@ internal class ProxyHelper
var response = Response.Create(responseMessage); var response = Response.Create(responseMessage);
return new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, _settings, request, response, 0, null, null, null, null, null, null); return new Mapping
(
guid: Guid.NewGuid(),
title: $"Proxy Mapping for {requestMessage.Method} {requestMessage.Path}",
description: string.Empty,
path: null,
settings: _settings,
request,
response,
priority: WireMockConstants.ProxyPriority, // This was 0
scenario: null,
executionConditionState: null,
nextState: null,
stateTimes: null,
webhooks: null,
timeSettings: null
);
} }
} }

View File

@@ -47,10 +47,10 @@ public class RequestMessage : IRequestMessage
public string Method { get; } public string Method { get; }
/// <inheritdoc cref="IRequestMessage.Headers" /> /// <inheritdoc cref="IRequestMessage.Headers" />
public IDictionary<string, WireMockList<string>>? Headers { get; } public IDictionary<string, WireMockList<string>> Headers { get; }
/// <inheritdoc cref="IRequestMessage.Cookies" /> /// <inheritdoc cref="IRequestMessage.Cookies" />
public IDictionary<string, string>? Cookies { get; } public IDictionary<string, string> Cookies { get; }
/// <inheritdoc cref="IRequestMessage.Query" /> /// <inheritdoc cref="IRequestMessage.Query" />
public IDictionary<string, WireMockList<string>>? Query { get; } public IDictionary<string, WireMockList<string>>? Query { get; }

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
using WireMock.Constants; using WireMock.Constants;
using WireMock.Http; using WireMock.Http;
@@ -15,6 +16,11 @@ internal static class ResponseMessageBuilder
{ HttpKnownHeaderNames.ContentType, new WireMockList<string> { WireMockConstants.ContentTypeJson } } { HttpKnownHeaderNames.ContentType, new WireMockList<string> { WireMockConstants.ContentTypeJson } }
}; };
internal static ResponseMessage Create(string? message, HttpStatusCode statusCode, Guid? guid = null)
{
return Create(message, (int)statusCode, guid);
}
internal static ResponseMessage Create(string? message, int statusCode = 200, Guid? guid = null) internal static ResponseMessage Create(string? message, int statusCode = 200, Guid? guid = null)
{ {
var response = new ResponseMessage var response = new ResponseMessage

View File

@@ -6,143 +6,142 @@ using WireMock.Matchers.Request;
using WireMock.ResponseBuilders; using WireMock.ResponseBuilders;
using WireMock.Types; using WireMock.Types;
namespace WireMock.Serialization namespace WireMock.Serialization;
internal static class LogEntryMapper
{ {
internal static class LogEntryMapper public static LogEntryModel Map(ILogEntry logEntry)
{ {
public static LogEntryModel Map(ILogEntry logEntry) var logRequestModel = new LogRequestModel
{ {
var logRequestModel = new LogRequestModel DateTime = logEntry.RequestMessage.DateTime,
{ ClientIP = logEntry.RequestMessage.ClientIP,
DateTime = logEntry.RequestMessage.DateTime, Path = logEntry.RequestMessage.Path,
ClientIP = logEntry.RequestMessage.ClientIP, AbsolutePath = logEntry.RequestMessage.AbsolutePath,
Path = logEntry.RequestMessage.Path, Url = logEntry.RequestMessage.Url,
AbsolutePath = logEntry.RequestMessage.AbsolutePath, AbsoluteUrl = logEntry.RequestMessage.AbsoluteUrl,
Url = logEntry.RequestMessage.Url, ProxyUrl = logEntry.RequestMessage.ProxyUrl,
AbsoluteUrl = logEntry.RequestMessage.AbsoluteUrl, Query = logEntry.RequestMessage.Query,
ProxyUrl = logEntry.RequestMessage.ProxyUrl, Method = logEntry.RequestMessage.Method,
Query = logEntry.RequestMessage.Query, Headers = logEntry.RequestMessage.Headers,
Method = logEntry.RequestMessage.Method, Cookies = logEntry.RequestMessage.Cookies
Headers = logEntry.RequestMessage.Headers, };
Cookies = logEntry.RequestMessage.Cookies
};
if (logEntry.RequestMessage.BodyData != null) if (logEntry.RequestMessage.BodyData != null)
{ {
logRequestModel.DetectedBodyType = logEntry.RequestMessage.BodyData.DetectedBodyType?.ToString(); logRequestModel.DetectedBodyType = logEntry.RequestMessage.BodyData.DetectedBodyType?.ToString();
logRequestModel.DetectedBodyTypeFromContentType = logEntry.RequestMessage.BodyData.DetectedBodyTypeFromContentType?.ToString(); logRequestModel.DetectedBodyTypeFromContentType = logEntry.RequestMessage.BodyData.DetectedBodyTypeFromContentType?.ToString();
switch (logEntry.RequestMessage.BodyData.DetectedBodyType) switch (logEntry.RequestMessage.BodyData.DetectedBodyType)
{
case BodyType.String:
logRequestModel.Body = logEntry.RequestMessage.BodyData.BodyAsString;
break;
case BodyType.Json:
logRequestModel.Body = logEntry.RequestMessage.BodyData.BodyAsString; // In case of Json, do also save the Body as string (backwards compatible)
logRequestModel.BodyAsJson = logEntry.RequestMessage.BodyData.BodyAsJson;
break;
case BodyType.Bytes:
logRequestModel.BodyAsBytes = logEntry.RequestMessage.BodyData.BodyAsBytes;
break;
}
logRequestModel.BodyEncoding = logEntry.RequestMessage.BodyData.Encoding != null
? new EncodingModel
{ {
case BodyType.String: EncodingName = logEntry.RequestMessage.BodyData.Encoding.EncodingName,
logRequestModel.Body = logEntry.RequestMessage.BodyData.BodyAsString; CodePage = logEntry.RequestMessage.BodyData.Encoding.CodePage,
break; WebName = logEntry.RequestMessage.BodyData.Encoding.WebName
case BodyType.Json:
logRequestModel.Body = logEntry.RequestMessage.BodyData.BodyAsString; // In case of Json, do also save the Body as string (backwards compatible)
logRequestModel.BodyAsJson = logEntry.RequestMessage.BodyData.BodyAsJson;
break;
case BodyType.Bytes:
logRequestModel.BodyAsBytes = logEntry.RequestMessage.BodyData.BodyAsBytes;
break;
} }
: null;
logRequestModel.BodyEncoding = logEntry.RequestMessage.BodyData.Encoding != null
? new EncodingModel
{
EncodingName = logEntry.RequestMessage.BodyData.Encoding.EncodingName,
CodePage = logEntry.RequestMessage.BodyData.Encoding.CodePage,
WebName = logEntry.RequestMessage.BodyData.Encoding.WebName
}
: null;
}
var logResponseModel = new LogResponseModel
{
StatusCode = logEntry.ResponseMessage.StatusCode,
Headers = logEntry.ResponseMessage.Headers
};
if (logEntry.ResponseMessage.FaultType != FaultType.NONE)
{
logResponseModel.FaultType = logEntry.ResponseMessage.FaultType.ToString();
logResponseModel.FaultPercentage = logEntry.ResponseMessage.FaultPercentage;
}
if (logEntry.ResponseMessage.BodyData != null)
{
logResponseModel.BodyOriginal = logEntry.ResponseMessage.BodyOriginal;
logResponseModel.BodyDestination = logEntry.ResponseMessage.BodyDestination;
logResponseModel.DetectedBodyType = logEntry.ResponseMessage.BodyData.DetectedBodyType;
logResponseModel.DetectedBodyTypeFromContentType = logEntry.ResponseMessage.BodyData.DetectedBodyTypeFromContentType;
switch (logEntry.ResponseMessage.BodyData.DetectedBodyType)
{
case BodyType.String:
logResponseModel.Body = logEntry.ResponseMessage.BodyData.BodyAsString;
break;
case BodyType.Json:
logResponseModel.BodyAsJson = logEntry.ResponseMessage.BodyData.BodyAsJson;
break;
case BodyType.Bytes:
logResponseModel.BodyAsBytes = logEntry.ResponseMessage.BodyData.BodyAsBytes;
break;
case BodyType.File:
logResponseModel.BodyAsFile = logEntry.ResponseMessage.BodyData.BodyAsFile;
logResponseModel.BodyAsFileIsCached = logEntry.ResponseMessage.BodyData.BodyAsFileIsCached;
break;
}
logResponseModel.BodyEncoding = logEntry.ResponseMessage.BodyData.Encoding != null
? new EncodingModel
{
EncodingName = logEntry.ResponseMessage.BodyData.Encoding.EncodingName,
CodePage = logEntry.ResponseMessage.BodyData.Encoding.CodePage,
WebName = logEntry.ResponseMessage.BodyData.Encoding.WebName
}
: null;
}
return new LogEntryModel
{
Guid = logEntry.Guid,
Request = logRequestModel,
Response = logResponseModel,
MappingGuid = logEntry.MappingGuid,
MappingTitle = logEntry.MappingTitle,
RequestMatchResult = Map(logEntry.RequestMatchResult),
PartialMappingGuid = logEntry.PartialMappingGuid,
PartialMappingTitle = logEntry.PartialMappingTitle,
PartialRequestMatchResult = Map(logEntry.PartialMatchResult)
};
} }
private static LogRequestMatchModel Map(IRequestMatchResult matchResult) var logResponseModel = new LogResponseModel
{ {
if (matchResult == null) StatusCode = logEntry.ResponseMessage.StatusCode,
Headers = logEntry.ResponseMessage.Headers
};
if (logEntry.ResponseMessage.FaultType != FaultType.NONE)
{
logResponseModel.FaultType = logEntry.ResponseMessage.FaultType.ToString();
logResponseModel.FaultPercentage = logEntry.ResponseMessage.FaultPercentage;
}
if (logEntry.ResponseMessage.BodyData != null)
{
logResponseModel.BodyOriginal = logEntry.ResponseMessage.BodyOriginal;
logResponseModel.BodyDestination = logEntry.ResponseMessage.BodyDestination;
logResponseModel.DetectedBodyType = logEntry.ResponseMessage.BodyData.DetectedBodyType;
logResponseModel.DetectedBodyTypeFromContentType = logEntry.ResponseMessage.BodyData.DetectedBodyTypeFromContentType;
switch (logEntry.ResponseMessage.BodyData.DetectedBodyType)
{ {
return null; case BodyType.String:
logResponseModel.Body = logEntry.ResponseMessage.BodyData.BodyAsString;
break;
case BodyType.Json:
logResponseModel.BodyAsJson = logEntry.ResponseMessage.BodyData.BodyAsJson;
break;
case BodyType.Bytes:
logResponseModel.BodyAsBytes = logEntry.ResponseMessage.BodyData.BodyAsBytes;
break;
case BodyType.File:
logResponseModel.BodyAsFile = logEntry.ResponseMessage.BodyData.BodyAsFile;
logResponseModel.BodyAsFileIsCached = logEntry.ResponseMessage.BodyData.BodyAsFileIsCached;
break;
} }
return new LogRequestMatchModel logResponseModel.BodyEncoding = logEntry.ResponseMessage.BodyData.Encoding != null
{ ? new EncodingModel
IsPerfectMatch = matchResult.IsPerfectMatch,
TotalScore = matchResult.TotalScore,
TotalNumber = matchResult.TotalNumber,
AverageTotalScore = matchResult.AverageTotalScore,
MatchDetails = matchResult.MatchDetails.Select(md => new
{ {
Name = md.MatcherType.Name.Replace("RequestMessage", string.Empty), EncodingName = logEntry.ResponseMessage.BodyData.Encoding.EncodingName,
Score = md.Score CodePage = logEntry.ResponseMessage.BodyData.Encoding.CodePage,
} as object).ToList() WebName = logEntry.ResponseMessage.BodyData.Encoding.WebName
}; }
: null;
} }
return new LogEntryModel
{
Guid = logEntry.Guid,
Request = logRequestModel,
Response = logResponseModel,
MappingGuid = logEntry.MappingGuid,
MappingTitle = logEntry.MappingTitle,
RequestMatchResult = Map(logEntry.RequestMatchResult),
PartialMappingGuid = logEntry.PartialMappingGuid,
PartialMappingTitle = logEntry.PartialMappingTitle,
PartialRequestMatchResult = Map(logEntry.PartialMatchResult)
};
}
private static LogRequestMatchModel? Map(IRequestMatchResult? matchResult)
{
if (matchResult == null)
{
return null;
}
return new LogRequestMatchModel
{
IsPerfectMatch = matchResult.IsPerfectMatch,
TotalScore = matchResult.TotalScore,
TotalNumber = matchResult.TotalNumber,
AverageTotalScore = matchResult.AverageTotalScore,
MatchDetails = matchResult.MatchDetails.Select(md => new
{
Name = md.MatcherType.Name.Replace("RequestMessage", string.Empty),
Score = md.Score
} as object).ToList()
};
} }
} }

View File

@@ -1,27 +1,26 @@
using WireMock.Models; using WireMock.Models;
namespace WireMock.Serialization namespace WireMock.Serialization;
{
internal static class TimeSettingsMapper
{
public static TimeSettingsModel Map(ITimeSettings settings)
{
return settings != null ? new TimeSettingsModel
{
Start = settings.Start,
End = settings.End,
TTL = settings.TTL
} : null;
}
public static ITimeSettings Map(TimeSettingsModel settings) internal static class TimeSettingsMapper
{
public static TimeSettingsModel? Map(ITimeSettings? settings)
{
return settings != null ? new TimeSettingsModel
{ {
return settings != null ? new TimeSettings Start = settings.Start,
{ End = settings.End,
Start = settings.Start, TTL = settings.TTL
End = settings.End, } : null;
TTL = settings.TTL }
} : null;
} public static ITimeSettings? Map(TimeSettingsModel? settings)
{
return settings != null ? new TimeSettings
{
Start = settings.Start,
End = settings.End,
TTL = settings.TTL
} : null;
} }
} }

View File

@@ -20,7 +20,6 @@ using WireMock.Matchers;
using WireMock.Matchers.Request; using WireMock.Matchers.Request;
using WireMock.Proxy; using WireMock.Proxy;
using WireMock.RequestBuilders; using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.ResponseProviders; using WireMock.ResponseProviders;
using WireMock.Serialization; using WireMock.Serialization;
using WireMock.Settings; using WireMock.Settings;
@@ -43,10 +42,10 @@ public partial class WireMockServer
private const string AdminScenarios = "/__admin/scenarios"; private const string AdminScenarios = "/__admin/scenarios";
private const string QueryParamReloadStaticMappings = "reloadStaticMappings"; private const string QueryParamReloadStaticMappings = "reloadStaticMappings";
private readonly Guid _proxyMappingGuid = new("e59914fd-782e-428e-91c1-4810ffb86567"); private static readonly Guid ProxyMappingGuid = new("e59914fd-782e-428e-91c1-4810ffb86567");
private readonly RegexMatcher _adminRequestContentTypeJson = new ContentTypeMatcher(WireMockConstants.ContentTypeJson, true); private static readonly RegexMatcher AdminRequestContentTypeJson = new ContentTypeMatcher(WireMockConstants.ContentTypeJson, true);
private readonly RegexMatcher _adminMappingsGuidPathMatcher = new(@"^\/__admin\/mappings\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$"); private static readonly RegexMatcher AdminMappingsGuidPathMatcher = new(@"^\/__admin\/mappings\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$");
private readonly RegexMatcher _adminRequestsGuidPathMatcher = new(@"^\/__admin\/requests\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$"); private static readonly RegexMatcher AdminRequestsGuidPathMatcher = new(@"^\/__admin\/requests\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$");
private EnhancedFileSystemWatcher? _enhancedFileSystemWatcher; private EnhancedFileSystemWatcher? _enhancedFileSystemWatcher;
@@ -55,21 +54,21 @@ public partial class WireMockServer
{ {
// __admin/settings // __admin/settings
Given(Request.Create().WithPath(AdminSettings).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(SettingsGet)); Given(Request.Create().WithPath(AdminSettings).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(SettingsGet));
Given(Request.Create().WithPath(AdminSettings).UsingMethod("PUT", "POST").WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(SettingsUpdate)); Given(Request.Create().WithPath(AdminSettings).UsingMethod("PUT", "POST").WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(SettingsUpdate));
// __admin/mappings // __admin/mappings
Given(Request.Create().WithPath(AdminMappings).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsGet)); Given(Request.Create().WithPath(AdminMappings).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsGet));
Given(Request.Create().WithPath(AdminMappings).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPost)); Given(Request.Create().WithPath(AdminMappings).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPost));
Given(Request.Create().WithPath(AdminMappingsWireMockOrg).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPostWireMockOrg)); Given(Request.Create().WithPath(AdminMappingsWireMockOrg).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPostWireMockOrg));
Given(Request.Create().WithPath(AdminMappings).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsDelete)); Given(Request.Create().WithPath(AdminMappings).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsDelete));
// __admin/mappings/reset // __admin/mappings/reset
Given(Request.Create().WithPath(AdminMappings + "/reset").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsReset)); Given(Request.Create().WithPath(AdminMappings + "/reset").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsReset));
// __admin/mappings/{guid} // __admin/mappings/{guid}
Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingGet)); Given(Request.Create().WithPath(AdminMappingsGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingGet));
Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingPut().WithHeader(HttpKnownHeaderNames.ContentType, _adminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingPut)); Given(Request.Create().WithPath(AdminMappingsGuidPathMatcher).UsingPut().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingPut));
Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingDelete)); Given(Request.Create().WithPath(AdminMappingsGuidPathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingDelete));
// __admin/mappings/save // __admin/mappings/save
Given(Request.Create().WithPath($"{AdminMappings}/save").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsSave)); Given(Request.Create().WithPath($"{AdminMappings}/save").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsSave));
@@ -85,8 +84,8 @@ public partial class WireMockServer
Given(Request.Create().WithPath(AdminRequests + "/reset").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsDelete)); Given(Request.Create().WithPath(AdminRequests + "/reset").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsDelete));
// __admin/request/{guid} // __admin/request/{guid}
Given(Request.Create().WithPath(_adminRequestsGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestGet)); Given(Request.Create().WithPath(AdminRequestsGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestGet));
Given(Request.Create().WithPath(_adminRequestsGuidPathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestDelete)); Given(Request.Create().WithPath(AdminRequestsGuidPathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestDelete));
// __admin/requests/find // __admin/requests/find
Given(Request.Create().WithPath(AdminRequests + "/find").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsFind)); Given(Request.Create().WithPath(AdminRequests + "/find").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsFind));
@@ -186,7 +185,7 @@ public partial class WireMockServer
string filenameWithoutExtension = Path.GetFileNameWithoutExtension(path); string filenameWithoutExtension = Path.GetFileNameWithoutExtension(path);
if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out string value)) if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out var value))
{ {
var mappingModels = DeserializeJsonToArray<MappingModel>(value); var mappingModels = DeserializeJsonToArray<MappingModel>(value);
foreach (var mappingModel in mappingModels) foreach (var mappingModel in mappingModels)
@@ -216,13 +215,13 @@ public partial class WireMockServer
if (settings.ProxyAndRecordSettings == null) if (settings.ProxyAndRecordSettings == null)
{ {
_httpClientForProxy = null; _httpClientForProxy = null;
DeleteMapping(_proxyMappingGuid); DeleteMapping(ProxyMappingGuid);
return; return;
} }
_httpClientForProxy = HttpClientBuilder.Build(settings.ProxyAndRecordSettings); _httpClientForProxy = HttpClientBuilder.Build(settings.ProxyAndRecordSettings);
var proxyRespondProvider = Given(Request.Create().WithPath("/*").UsingAnyMethod()).WithGuid(_proxyMappingGuid); var proxyRespondProvider = Given(Request.Create().WithPath("/*").UsingAnyMethod()).WithGuid(ProxyMappingGuid).WithTitle("Default Proxy Mapping on /*");
if (settings.StartAdminInterface == true) if (settings.StartAdminInterface == true)
{ {
proxyRespondProvider.AtPriority(WireMockConstants.ProxyPriority); proxyRespondProvider.AtPriority(WireMockConstants.ProxyPriority);
@@ -359,7 +358,7 @@ public partial class WireMockServer
var mappingModel = DeserializeObject<MappingModel>(requestMessage); var mappingModel = DeserializeObject<MappingModel>(requestMessage);
Guid? guidFromPut = ConvertMappingAndRegisterAsRespondProvider(mappingModel, guid); Guid? guidFromPut = ConvertMappingAndRegisterAsRespondProvider(mappingModel, guid);
return ResponseMessageBuilder.Create("Mapping added or updated", 200, guidFromPut); return ResponseMessageBuilder.Create("Mapping added or updated", HttpStatusCode.OK, guidFromPut);
} }
private IResponseMessage MappingDelete(IRequestMessage requestMessage) private IResponseMessage MappingDelete(IRequestMessage requestMessage)
@@ -368,13 +367,13 @@ public partial class WireMockServer
if (DeleteMapping(guid)) if (DeleteMapping(guid))
{ {
return ResponseMessageBuilder.Create("Mapping removed", 200, guid); return ResponseMessageBuilder.Create("Mapping removed", HttpStatusCode.OK, guid);
} }
return ResponseMessageBuilder.Create("Mapping not found", 404); return ResponseMessageBuilder.Create("Mapping not found", HttpStatusCode.NotFound);
} }
private Guid ParseGuidFromRequestMessage(IRequestMessage requestMessage) private static Guid ParseGuidFromRequestMessage(IRequestMessage requestMessage)
{ {
return Guid.Parse(requestMessage.Path.Substring(AdminMappings.Length + 1)); return Guid.Parse(requestMessage.Path.Substring(AdminMappings.Length + 1));
} }

View File

@@ -27,7 +27,7 @@ public partial class WireMockServer
string filenameWithoutExtension = Path.GetFileNameWithoutExtension(path); string filenameWithoutExtension = Path.GetFileNameWithoutExtension(path);
if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out string value)) if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out var value))
{ {
var mappings = DeserializeJsonToArray<OrgMappings>(value); var mappings = DeserializeJsonToArray<OrgMappings>(value);
foreach (var mapping in mappings) foreach (var mapping in mappings)

View File

@@ -80,7 +80,7 @@ public partial class WireMockServer : IWireMockServer
/// Gets the scenarios. /// Gets the scenarios.
/// </summary> /// </summary>
[PublicAPI] [PublicAPI]
public ConcurrentDictionary<string, ScenarioState> Scenarios => new ConcurrentDictionary<string, ScenarioState>(_options.Scenarios); public ConcurrentDictionary<string, ScenarioState> Scenarios => new(_options.Scenarios);
#region IDisposable Members #region IDisposable Members
/// <summary> /// <summary>
@@ -414,10 +414,10 @@ public partial class WireMockServer : IWireMockServer
/// <inheritdoc cref="IWireMockServer.SetAzureADAuthentication(string, string)" /> /// <inheritdoc cref="IWireMockServer.SetAzureADAuthentication(string, string)" />
[PublicAPI] [PublicAPI]
public void SetAzureADAuthentication([NotNull] string tenant, [NotNull] string audience) public void SetAzureADAuthentication(string tenant, string audience)
{ {
Guard.NotNull(tenant, nameof(tenant)); Guard.NotNull(tenant);
Guard.NotNull(audience, nameof(audience)); Guard.NotNull(audience);
#if NETSTANDARD1_3 #if NETSTANDARD1_3
throw new NotSupportedException("AzureADAuthentication is not supported for NETStandard 1.3"); throw new NotSupportedException("AzureADAuthentication is not supported for NETStandard 1.3");
@@ -445,14 +445,14 @@ public partial class WireMockServer : IWireMockServer
/// <inheritdoc cref="IWireMockServer.SetMaxRequestLogCount" /> /// <inheritdoc cref="IWireMockServer.SetMaxRequestLogCount" />
[PublicAPI] [PublicAPI]
public void SetMaxRequestLogCount([CanBeNull] int? maxRequestLogCount) public void SetMaxRequestLogCount(int? maxRequestLogCount)
{ {
_options.MaxRequestLogCount = maxRequestLogCount; _options.MaxRequestLogCount = maxRequestLogCount;
} }
/// <inheritdoc cref="IWireMockServer.SetRequestLogExpirationDuration" /> /// <inheritdoc cref="IWireMockServer.SetRequestLogExpirationDuration" />
[PublicAPI] [PublicAPI]
public void SetRequestLogExpirationDuration([CanBeNull] int? requestLogExpirationDuration) public void SetRequestLogExpirationDuration(int? requestLogExpirationDuration)
{ {
_options.RequestLogExpirationDuration = requestLogExpirationDuration; _options.RequestLogExpirationDuration = requestLogExpirationDuration;
} }
@@ -542,12 +542,12 @@ public partial class WireMockServer : IWireMockServer
{ {
if (!string.IsNullOrEmpty(settings.AdminUsername) && !string.IsNullOrEmpty(settings.AdminPassword)) if (!string.IsNullOrEmpty(settings.AdminUsername) && !string.IsNullOrEmpty(settings.AdminPassword))
{ {
SetBasicAuthentication(settings.AdminUsername, settings.AdminPassword); SetBasicAuthentication(settings.AdminUsername!, settings.AdminPassword!);
} }
if (!string.IsNullOrEmpty(settings.AdminAzureADTenant) && !string.IsNullOrEmpty(settings.AdminAzureADAudience)) if (!string.IsNullOrEmpty(settings.AdminAzureADTenant) && !string.IsNullOrEmpty(settings.AdminAzureADAudience))
{ {
SetAzureADAuthentication(settings.AdminAzureADTenant, settings.AdminAzureADAudience); SetAzureADAuthentication(settings.AdminAzureADTenant!, settings.AdminAzureADAudience!);
} }
InitAdmin(); InitAdmin();

View File

@@ -1,230 +1,230 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
namespace WireMock.Util namespace WireMock.Util;
/// <summary>
/// Based on:
/// http://utf8checker.codeplex.com
/// https://github.com/0x53A/Mvvm/blob/master/src/Mvvm/src/Utf8Checker.cs
///
/// References:
/// http://anubis.dkuug.dk/JTC1/SC2/WG2/docs/n1335
/// http://www.cl.cam.ac.uk/~mgk25/ucs/ISO-10646-UTF-8.html
/// http://www.unicode.org/versions/corrigendum1.html
/// http://www.ietf.org/rfc/rfc2279.txt
/// </summary>
public static class BytesEncodingUtils
{ {
/// <summary> /// <summary>
/// Based on: /// Tries the get the Encoding from an array of bytes.
/// http://utf8checker.codeplex.com
/// https://github.com/0x53A/Mvvm/blob/master/src/Mvvm/src/Utf8Checker.cs
///
/// References:
/// http://anubis.dkuug.dk/JTC1/SC2/WG2/docs/n1335
/// http://www.cl.cam.ac.uk/~mgk25/ucs/ISO-10646-UTF-8.html
/// http://www.unicode.org/versions/corrigendum1.html
/// http://www.ietf.org/rfc/rfc2279.txt
/// </summary> /// </summary>
public static class BytesEncodingUtils /// <param name="bytes">The bytes.</param>
/// <param name="encoding">The output encoding.</param>
public static bool TryGetEncoding(byte[] bytes, [NotNullWhen(true)] out Encoding? encoding)
{ {
/// <summary> encoding = null;
/// Tries the get the Encoding from an array of bytes. if (bytes.All(b => b < 80))
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="encoding">The output encoding.</param>
public static bool TryGetEncoding(byte[] bytes, out Encoding encoding)
{ {
encoding = null; encoding = Encoding.ASCII;
if (bytes.All(b => b < 80))
{
encoding = Encoding.ASCII;
return true;
}
if (StartsWith(bytes, new byte[] { 0xff, 0xfe, 0x00, 0x00 }))
{
encoding = Encoding.UTF32;
return true;
}
if (StartsWith(bytes, new byte[] { 0xfe, 0xff }))
{
encoding = Encoding.BigEndianUnicode;
return true;
}
if (StartsWith(bytes, new byte[] { 0xff, 0xfe }))
{
encoding = Encoding.Unicode;
return true;
}
if (StartsWith(bytes, new byte[] { 0xef, 0xbb, 0xbf }))
{
encoding = Encoding.UTF8;
return true;
}
if (IsUtf8(bytes, bytes.Length))
{
encoding = new UTF8Encoding(false);
return true;
}
return false;
}
private static bool StartsWith(IEnumerable<byte> data, IReadOnlyCollection<byte> other)
{
byte[] arraySelf = data.Take(other.Count).ToArray();
return other.SequenceEqual(arraySelf);
}
private static bool IsUtf8(IReadOnlyList<byte> buffer, int length)
{
int position = 0;
int bytes = 0;
while (position < length)
{
if (!IsValid(buffer, position, length, ref bytes))
{
return false;
}
position += bytes;
}
return true; return true;
} }
#pragma warning disable S3776 // Cognitive Complexity of methods should not be too high if (StartsWith(bytes, new byte[] { 0xff, 0xfe, 0x00, 0x00 }))
private static bool IsValid(IReadOnlyList<byte> buffer, int position, int length, ref int bytes)
{ {
if (length > buffer.Count) encoding = Encoding.UTF32;
{ return true;
throw new ArgumentException("Invalid length"); }
}
if (position > length - 1) if (StartsWith(bytes, new byte[] { 0xfe, 0xff }))
{
encoding = Encoding.BigEndianUnicode;
return true;
}
if (StartsWith(bytes, new byte[] { 0xff, 0xfe }))
{
encoding = Encoding.Unicode;
return true;
}
if (StartsWith(bytes, new byte[] { 0xef, 0xbb, 0xbf }))
{
encoding = Encoding.UTF8;
return true;
}
if (IsUtf8(bytes, bytes.Length))
{
encoding = new UTF8Encoding(false);
return true;
}
return false;
}
private static bool StartsWith(IEnumerable<byte> data, IReadOnlyCollection<byte> other)
{
byte[] arraySelf = data.Take(other.Count).ToArray();
return other.SequenceEqual(arraySelf);
}
private static bool IsUtf8(IReadOnlyList<byte> buffer, int length)
{
int position = 0;
int bytes = 0;
while (position < length)
{
if (!IsValid(buffer, position, length, ref bytes))
{
return false;
}
position += bytes;
}
return true;
}
#pragma warning disable S3776 // Cognitive Complexity of methods should not be too high
private static bool IsValid(IReadOnlyList<byte> buffer, int position, int length, ref int bytes)
{
if (length > buffer.Count)
{
throw new ArgumentException("Invalid length");
}
if (position > length - 1)
{
bytes = 0;
return true;
}
byte ch = buffer[position];
if (ch <= 0x7F)
{
bytes = 1;
return true;
}
if (ch >= 0xc2 && ch <= 0xdf)
{
if (position >= length - 2)
{ {
bytes = 0; bytes = 0;
return true; return false;
} }
byte ch = buffer[position]; if (buffer[position + 1] < 0x80 || buffer[position + 1] > 0xbf)
if (ch <= 0x7F)
{ {
bytes = 1; bytes = 0;
return true; return false;
} }
if (ch >= 0xc2 && ch <= 0xdf) bytes = 2;
{ return true;
if (position >= length - 2)
{
bytes = 0;
return false;
}
if (buffer[position + 1] < 0x80 || buffer[position + 1] > 0xbf)
{
bytes = 0;
return false;
}
bytes = 2;
return true;
}
if (ch == 0xe0)
{
if (position >= length - 3)
{
bytes = 0;
return false;
}
if (buffer[position + 1] < 0xa0 || buffer[position + 1] > 0xbf ||
buffer[position + 2] < 0x80 || buffer[position + 2] > 0xbf)
{
bytes = 0;
return false;
}
bytes = 3;
return true;
}
if (ch >= 0xe1 && ch <= 0xef)
{
if (position >= length - 3)
{
bytes = 0;
return false;
}
if (buffer[position + 1] < 0x80 || buffer[position + 1] > 0xbf ||
buffer[position + 2] < 0x80 || buffer[position + 2] > 0xbf)
{
bytes = 0;
return false;
}
bytes = 3;
return true;
}
if (ch == 0xf0)
{
if (position >= length - 4)
{
bytes = 0;
return false;
}
if (buffer[position + 1] < 0x90 || buffer[position + 1] > 0xbf ||
buffer[position + 2] < 0x80 || buffer[position + 2] > 0xbf ||
buffer[position + 3] < 0x80 || buffer[position + 3] > 0xbf)
{
bytes = 0;
return false;
}
bytes = 4;
return true;
}
if (ch == 0xf4)
{
if (position >= length - 4)
{
bytes = 0;
return false;
}
if (buffer[position + 1] < 0x80 || buffer[position + 1] > 0x8f ||
buffer[position + 2] < 0x80 || buffer[position + 2] > 0xbf ||
buffer[position + 3] < 0x80 || buffer[position + 3] > 0xbf)
{
bytes = 0;
return false;
}
bytes = 4;
return true;
}
if (ch >= 0xf1 && ch <= 0xf3)
{
if (position >= length - 4)
{
bytes = 0;
return false;
}
if (buffer[position + 1] < 0x80 || buffer[position + 1] > 0xbf ||
buffer[position + 2] < 0x80 || buffer[position + 2] > 0xbf ||
buffer[position + 3] < 0x80 || buffer[position + 3] > 0xbf)
{
bytes = 0;
return false;
}
bytes = 4;
return true;
}
return false;
} }
if (ch == 0xe0)
{
if (position >= length - 3)
{
bytes = 0;
return false;
}
if (buffer[position + 1] < 0xa0 || buffer[position + 1] > 0xbf ||
buffer[position + 2] < 0x80 || buffer[position + 2] > 0xbf)
{
bytes = 0;
return false;
}
bytes = 3;
return true;
}
if (ch >= 0xe1 && ch <= 0xef)
{
if (position >= length - 3)
{
bytes = 0;
return false;
}
if (buffer[position + 1] < 0x80 || buffer[position + 1] > 0xbf ||
buffer[position + 2] < 0x80 || buffer[position + 2] > 0xbf)
{
bytes = 0;
return false;
}
bytes = 3;
return true;
}
if (ch == 0xf0)
{
if (position >= length - 4)
{
bytes = 0;
return false;
}
if (buffer[position + 1] < 0x90 || buffer[position + 1] > 0xbf ||
buffer[position + 2] < 0x80 || buffer[position + 2] > 0xbf ||
buffer[position + 3] < 0x80 || buffer[position + 3] > 0xbf)
{
bytes = 0;
return false;
}
bytes = 4;
return true;
}
if (ch == 0xf4)
{
if (position >= length - 4)
{
bytes = 0;
return false;
}
if (buffer[position + 1] < 0x80 || buffer[position + 1] > 0x8f ||
buffer[position + 2] < 0x80 || buffer[position + 2] > 0xbf ||
buffer[position + 3] < 0x80 || buffer[position + 3] > 0xbf)
{
bytes = 0;
return false;
}
bytes = 4;
return true;
}
if (ch >= 0xf1 && ch <= 0xf3)
{
if (position >= length - 4)
{
bytes = 0;
return false;
}
if (buffer[position + 1] < 0x80 || buffer[position + 1] > 0xbf ||
buffer[position + 2] < 0x80 || buffer[position + 2] > 0xbf ||
buffer[position + 3] < 0x80 || buffer[position + 3] > 0xbf)
{
bytes = 0;
return false;
}
bytes = 4;
return true;
}
return false;
} }
#pragma warning restore S3776 // Cognitive Complexity of methods should not be too high }
} #pragma warning restore S3776 // Cognitive Complexity of methods should not be too high

View File

@@ -1,49 +1,39 @@
using System; using System;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
namespace WireMock.Util namespace WireMock.Util;
internal static class CompressionUtils
{ {
internal static class CompressionUtils public static byte[] Compress(string contentEncoding, byte[] data)
{ {
public static byte[] Compress(string contentEncoding, byte[] data) using var compressedStream = new MemoryStream();
{ using var zipStream = Create(contentEncoding, compressedStream, CompressionMode.Compress);
using (var compressedStream = new MemoryStream()) zipStream.Write(data, 0, data.Length);
using (var zipStream = Create(contentEncoding, compressedStream, CompressionMode.Compress))
{
zipStream.Write(data, 0, data.Length);
#if !NETSTANDARD1_3 #if !NETSTANDARD1_3
zipStream.Close(); zipStream.Close();
#endif #endif
return compressedStream.ToArray(); return compressedStream.ToArray();
} }
}
public static byte[] Decompress(string contentEncoding, byte[] data) public static byte[] Decompress(string contentEncoding, byte[] data)
{
using var compressedStream = new MemoryStream(data);
using var zipStream = Create(contentEncoding, compressedStream, CompressionMode.Decompress);
using var resultStream = new MemoryStream();
zipStream.CopyTo(resultStream);
return resultStream.ToArray();
}
private static Stream Create(string contentEncoding, Stream stream, CompressionMode mode)
{
return contentEncoding switch
{ {
using (var compressedStream = new MemoryStream(data)) "gzip" => new GZipStream(stream, mode),
using (var zipStream = Create(contentEncoding, compressedStream, CompressionMode.Decompress)) "deflate" => new DeflateStream(stream, mode),
using (var resultStream = new MemoryStream()) _ => throw new NotSupportedException($"ContentEncoding '{contentEncoding}' is not supported.")
{ };
zipStream.CopyTo(resultStream);
return resultStream.ToArray();
}
}
private static Stream Create(string contentEncoding, Stream stream, CompressionMode mode)
{
switch (contentEncoding)
{
case "gzip":
return new GZipStream(stream, mode);
case "deflate":
return new DeflateStream(stream, mode);
default:
throw new NotSupportedException($"ContentEncoding '{contentEncoding}' is not supported.");
}
}
} }
} }

View File

@@ -1,32 +1,30 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using JetBrains.Annotations;
using Stef.Validation; using Stef.Validation;
namespace WireMock.Util namespace WireMock.Util;
/// <summary>
/// Some IDictionary Extensions
/// </summary>
public static class DictionaryExtensions
{ {
/// <summary> /// <summary>
/// Some IDictionary Extensions /// Loops the dictionary and executes the specified action.
/// </summary> /// </summary>
public static class DictionaryExtensions /// <typeparam name="TKey">The type of the key.</typeparam>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <param name="dictionary">The dictionary to loop (can be null).</param>
/// <param name="action">The action.</param>
public static void Loop<TKey, TValue>(this IDictionary<TKey, TValue>? dictionary, Action<TKey, TValue> action)
{ {
/// <summary> Guard.NotNull(action);
/// Loops the dictionary and executes the specified action.
/// </summary>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <param name="dictionary">The dictionary to loop (can be null).</param>
/// <param name="action">The action.</param>
public static void Loop<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, [NotNull] Action<TKey, TValue> action)
{
Guard.NotNull(action, nameof(action));
if (dictionary != null) if (dictionary != null)
{
foreach (var entry in dictionary)
{ {
foreach (var entry in dictionary) action(entry.Key, entry.Value);
{
action(entry.Key, entry.Value);
}
} }
} }
} }

View File

@@ -1,36 +1,35 @@
using JetBrains.Annotations; using System.Diagnostics.CodeAnalysis;
using System.Threading; using System.Threading;
using WireMock.Handlers;
using Stef.Validation; using Stef.Validation;
using WireMock.Handlers;
namespace WireMock.Util namespace WireMock.Util;
internal static class FileHelper
{ {
internal static class FileHelper private const int NumberOfRetries = 3;
private const int DelayOnRetry = 500;
public static bool TryReadMappingFileWithRetryAndDelay(IFileSystemHandler handler, string path, [NotNullWhen(true)] out string? value)
{ {
private const int NumberOfRetries = 3; Guard.NotNull(handler);
private const int DelayOnRetry = 500; Guard.NotNullOrEmpty(path);
public static bool TryReadMappingFileWithRetryAndDelay([NotNull] IFileSystemHandler handler, [NotNull] string path, out string value) value = null;
for (int i = 1; i <= NumberOfRetries; ++i)
{ {
Guard.NotNull(handler, nameof(handler)); try
Guard.NotNullOrEmpty(path, nameof(path));
value = null;
for (int i = 1; i <= NumberOfRetries; ++i)
{ {
try value = handler.ReadMappingFile(path);
{ return true;
value = handler.ReadMappingFile(path); }
return true; catch
} {
catch Thread.Sleep(DelayOnRetry);
{
Thread.Sleep(DelayOnRetry);
}
} }
return false;
} }
return false;
} }
} }

View File

@@ -1,91 +1,90 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace WireMock.Util namespace WireMock.Util;
/// <summary>
/// Based on https://github.com/tmenier/Flurl/blob/129565361e135e639f1d44a35a78aea4302ac6ca/src/Flurl.Http/HttpStatusRangeParser.cs
/// </summary>
internal static class HttpStatusRangeParser
{ {
/// <summary> /// <summary>
/// Based on https://github.com/tmenier/Flurl/blob/129565361e135e639f1d44a35a78aea4302ac6ca/src/Flurl.Http/HttpStatusRangeParser.cs /// Determines whether the specified pattern is match.
/// </summary> /// </summary>
internal static class HttpStatusRangeParser /// <param name="pattern">The pattern. (Can be null, in that case it's allowed.)</param>
/// <param name="httpStatusCode">The value.</param>
/// <exception cref="ArgumentException"><paramref name="pattern"/> is invalid.</exception>
public static bool IsMatch(string pattern, object httpStatusCode)
{ {
/// <summary> switch (httpStatusCode)
/// Determines whether the specified pattern is match.
/// </summary>
/// <param name="pattern">The pattern. (Can be null, in that case it's allowed.)</param>
/// <param name="httpStatusCode">The value.</param>
/// <exception cref="ArgumentException"><paramref name="pattern"/> is invalid.</exception>
public static bool IsMatch(string pattern, object httpStatusCode)
{ {
switch (httpStatusCode) case int statusCodeAsInteger:
{ return IsMatch(pattern, statusCodeAsInteger);
case int statusCodeAsInteger:
return IsMatch(pattern, statusCodeAsInteger);
case string statusCodeAsString: case string statusCodeAsString:
return IsMatch(pattern, int.Parse(statusCodeAsString)); return IsMatch(pattern, int.Parse(statusCodeAsString));
}
return false;
}
/// <summary>
/// Determines whether the specified pattern is match.
/// </summary>
/// <param name="pattern">The pattern. (Can be null, in that case it's allowed.)</param>
/// <param name="httpStatusCode">The value.</param>
/// <exception cref="ArgumentException"><paramref name="pattern"/> is invalid.</exception>
public static bool IsMatch(string pattern, HttpStatusCode httpStatusCode)
{
return IsMatch(pattern, (int)httpStatusCode);
}
/// <summary>
/// Determines whether the specified pattern is match.
/// </summary>
/// <param name="pattern">The pattern. (Can be null, in that case it's allowed.)</param>
/// <param name="httpStatusCode">The value.</param>
/// <exception cref="ArgumentException"><paramref name="pattern"/> is invalid.</exception>
public static bool IsMatch(string? pattern, int httpStatusCode)
{
if (pattern == null)
{
return true;
}
foreach (var range in pattern.Split(',').Select(p => p.Trim()))
{
switch (range)
{
case "":
continue;
case "*":
return true; // special case - allow everything
} }
return false; string[] bounds = range.Split('-');
} int lower = 0;
int upper = 0;
/// <summary> bool valid =
/// Determines whether the specified pattern is match. bounds.Length <= 2 &&
/// </summary> int.TryParse(Regex.Replace(bounds.First().Trim(), "[*xX]", "0"), out lower) &&
/// <param name="pattern">The pattern. (Can be null, in that case it's allowed.)</param> int.TryParse(Regex.Replace(bounds.Last().Trim(), "[*xX]", "9"), out upper);
/// <param name="httpStatusCode">The value.</param>
/// <exception cref="ArgumentException"><paramref name="pattern"/> is invalid.</exception>
public static bool IsMatch(string pattern, HttpStatusCode httpStatusCode)
{
return IsMatch(pattern, (int)httpStatusCode);
}
/// <summary> if (!valid)
/// Determines whether the specified pattern is match. {
/// </summary> throw new ArgumentException($"Invalid range pattern: \"{pattern}\". Examples of allowed patterns: \"400\", \"4xx\", \"300,400-403\", \"*\".");
/// <param name="pattern">The pattern. (Can be null, in that case it's allowed.)</param> }
/// <param name="httpStatusCode">The value.</param>
/// <exception cref="ArgumentException"><paramref name="pattern"/> is invalid.</exception> if (httpStatusCode >= lower && httpStatusCode <= upper)
public static bool IsMatch(string pattern, int httpStatusCode)
{
if (pattern == null)
{ {
return true; return true;
} }
foreach (var range in pattern.Split(',').Select(p => p.Trim()))
{
switch (range)
{
case "":
continue;
case "*":
return true; // special case - allow everything
}
string[] bounds = range.Split('-');
int lower = 0;
int upper = 0;
bool valid =
bounds.Length <= 2 &&
int.TryParse(Regex.Replace(bounds.First().Trim(), "[*xX]", "0"), out lower) &&
int.TryParse(Regex.Replace(bounds.Last().Trim(), "[*xX]", "9"), out upper);
if (!valid)
{
throw new ArgumentException($"Invalid range pattern: \"{pattern}\". Examples of allowed patterns: \"400\", \"4xx\", \"300,400-403\", \"*\".");
}
if (httpStatusCode >= lower && httpStatusCode <= upper)
{
return true;
}
}
return false;
} }
return false;
} }
} }

View File

@@ -1,58 +1,58 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace WireMock.Util namespace WireMock.Util;
/// <summary>
/// Port Utility class
/// </summary>
public static class PortUtils
{ {
private static readonly Regex UrlDetailsRegex = new(@"^((?<proto>\w+)://)(?<host>[^/]+?):(?<port>\d+)\/?$", RegexOptions.Compiled);
/// <summary> /// <summary>
/// Port Utility class /// Finds a free TCP port.
/// </summary> /// </summary>
public static class PortUtils /// <remarks>see http://stackoverflow.com/questions/138043/find-the-next-tcp-port-in-net.</remarks>
public static int FindFreeTcpPort()
{ {
private static readonly Regex UrlDetailsRegex = new Regex(@"^((?<proto>\w+)://)(?<host>[^/]+?):(?<port>\d+)\/?$", RegexOptions.Compiled); TcpListener? tcpListener = null;
try
/// <summary>
/// Finds a free TCP port.
/// </summary>
/// <remarks>see http://stackoverflow.com/questions/138043/find-the-next-tcp-port-in-net.</remarks>
public static int FindFreeTcpPort()
{ {
TcpListener tcpListener = null; tcpListener = new TcpListener(IPAddress.Loopback, 0);
try tcpListener.Start();
{
tcpListener = new TcpListener(IPAddress.Loopback, 0);
tcpListener.Start();
return ((IPEndPoint)tcpListener.LocalEndpoint).Port; return ((IPEndPoint)tcpListener.LocalEndpoint).Port;
}
finally
{
tcpListener?.Stop();
}
} }
finally
/// <summary>
/// Extract the if-isHttps, protocol, host and port from a URL.
/// </summary>
public static bool TryExtract(string url, out bool isHttps, out string protocol, out string host, out int port)
{ {
isHttps = false; tcpListener?.Stop();
protocol = null;
host = null;
port = default;
var match = UrlDetailsRegex.Match(url);
if (match.Success)
{
protocol = match.Groups["proto"].Value;
isHttps = protocol.StartsWith("https", StringComparison.OrdinalIgnoreCase);
host = match.Groups["host"].Value;
return int.TryParse(match.Groups["port"].Value, out port);
}
return false;
} }
} }
/// <summary>
/// Extract the if-isHttps, protocol, host and port from a URL.
/// </summary>
public static bool TryExtract(string url, out bool isHttps, [NotNullWhen(true)] out string? protocol, [NotNullWhen(true)] out string? host, out int port)
{
isHttps = false;
protocol = null;
host = null;
port = default;
var match = UrlDetailsRegex.Match(url);
if (match.Success)
{
protocol = match.Groups["proto"].Value;
isHttps = protocol.StartsWith("https", StringComparison.OrdinalIgnoreCase);
host = match.Groups["host"].Value;
return int.TryParse(match.Groups["port"].Value, out port);
}
return false;
}
} }

View File

@@ -1,38 +1,37 @@
using System; using System;
using System.Net; using System.Net;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using WireMock.Types; using WireMock.Types;
namespace WireMock.Util namespace WireMock.Util;
/// <summary>
/// Based on https://stackoverflow.com/questions/659887/get-url-parameters-from-a-string-in-net
/// </summary>
internal static class QueryStringParser
{ {
/// <summary> public static IDictionary<string, WireMockList<string>> Parse(string queryString)
/// Based on https://stackoverflow.com/questions/659887/get-url-parameters-from-a-string-in-net
/// </summary>
internal static class QueryStringParser
{ {
public static IDictionary<string, WireMockList<string>> Parse(string queryString) if (string.IsNullOrEmpty(queryString))
{ {
if (string.IsNullOrEmpty(queryString)) return new Dictionary<string, WireMockList<string>>();
{
return new Dictionary<string, WireMockList<string>>();
}
string[] JoinParts(string[] parts)
{
if (parts.Length > 1)
{
return parts[1].Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); // support "?key=1,2"
}
return new string[0];
}
return queryString.TrimStart('?')
.Split(new[] { '&', ';' }, StringSplitOptions.RemoveEmptyEntries) // Support "?key=value;key=anotherValue" and "?key=value&key=anotherValue"
.Select(parameter => parameter.Split(new[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries))
.GroupBy(parts => parts[0], JoinParts)
.ToDictionary(grouping => grouping.Key, grouping => new WireMockList<string>(grouping.SelectMany(x => x).Select(WebUtility.UrlDecode)));
} }
string[] JoinParts(string[] parts)
{
if (parts.Length > 1)
{
return parts[1].Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); // support "?key=1,2"
}
return new string[0];
}
return queryString.TrimStart('?')
.Split(new[] { '&', ';' }, StringSplitOptions.RemoveEmptyEntries) // Support "?key=value;key=anotherValue" and "?key=value&key=anotherValue"
.Select(parameter => parameter.Split(new[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries))
.GroupBy(parts => parts[0], JoinParts)
.ToDictionary(grouping => grouping.Key, grouping => new WireMockList<string>(grouping.SelectMany(x => x).Select(WebUtility.UrlDecode)));
} }
} }

View File

@@ -14,8 +14,8 @@ internal static class TypeBuilderUtils
{ {
private static readonly ConcurrentDictionary<IDictionary<string, Type>, Type> Types = new(); private static readonly ConcurrentDictionary<IDictionary<string, Type>, Type> Types = new();
private static readonly ModuleBuilder ModuleBuilder = private static readonly ModuleBuilder ModuleBuilder = AssemblyBuilder
AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("WireMock.Net.Reflection"), AssemblyBuilderAccess.Run) .DefineDynamicAssembly(new AssemblyName("WireMock.Net.Reflection"), AssemblyBuilderAccess.Run)
.DefineDynamicModule("WireMock.Net.Reflection.Module"); .DefineDynamicModule("WireMock.Net.Reflection.Module");
public static Type BuildType(IDictionary<string, Type> properties, string? name = null) public static Type BuildType(IDictionary<string, Type> properties, string? name = null)

View File

@@ -1,5 +1,4 @@
using System; using System;
using JetBrains.Annotations;
using WireMock.Models; using WireMock.Models;
using Stef.Validation; using Stef.Validation;
#if !USE_ASPNETCORE #if !USE_ASPNETCORE
@@ -8,34 +7,33 @@ using Microsoft.Owin;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
#endif #endif
namespace WireMock.Util namespace WireMock.Util;
internal static class UrlUtils
{ {
internal static class UrlUtils public static UrlDetails Parse(Uri uri, PathString pathBase)
{ {
public static UrlDetails Parse([NotNull] Uri uri, PathString pathBase) Guard.NotNull(uri);
if (!pathBase.HasValue)
{ {
Guard.NotNull(uri, nameof(uri)); return new UrlDetails(uri, uri);
if (!pathBase.HasValue)
{
return new UrlDetails(uri, uri);
}
var builder = new UriBuilder(uri);
builder.Path = RemoveFirst(builder.Path, pathBase.Value);
return new UrlDetails(uri, builder.Uri);
} }
private static string RemoveFirst(string text, string search) var builder = new UriBuilder(uri);
{ builder.Path = RemoveFirst(builder.Path, pathBase.Value);
int pos = text.IndexOf(search, StringComparison.Ordinal);
if (pos < 0)
{
return text;
}
return text.Substring(0, pos) + text.Substring(pos + search.Length); return new UrlDetails(uri, builder.Uri);
}
} }
}
private static string RemoveFirst(string text, string search)
{
int pos = text.IndexOf(search, StringComparison.Ordinal);
if (pos < 0)
{
return text;
}
return text.Substring(0, pos) + text.Substring(pos + search.Length);
}
}

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using FluentAssertions; using FluentAssertions;
using Moq; using Moq;
@@ -9,183 +9,195 @@ using WireMock.Owin;
using WireMock.Util; using WireMock.Util;
using Xunit; using Xunit;
namespace WireMock.Net.Tests.Owin namespace WireMock.Net.Tests.Owin;
public class MappingMatcherTests
{ {
public class MappingMatcherTests private readonly Mock<IWireMockMiddlewareOptions> _optionsMock;
private readonly MappingMatcher _sut;
public MappingMatcherTests()
{ {
private readonly Mock<IWireMockMiddlewareOptions> _optionsMock; _optionsMock = new Mock<IWireMockMiddlewareOptions>();
private readonly MappingMatcher _sut; _optionsMock.SetupAllProperties();
_optionsMock.Setup(o => o.Mappings).Returns(new ConcurrentDictionary<Guid, IMapping>());
_optionsMock.Setup(o => o.LogEntries).Returns(new ConcurrentObservableCollection<LogEntry>());
_optionsMock.Setup(o => o.Scenarios).Returns(new ConcurrentDictionary<string, ScenarioState>());
public MappingMatcherTests() var loggerMock = new Mock<IWireMockLogger>();
loggerMock.SetupAllProperties();
loggerMock.Setup(l => l.Error(It.IsAny<string>()));
_optionsMock.Setup(o => o.Logger).Returns(loggerMock.Object);
_sut = new MappingMatcher(_optionsMock.Object);
}
[Fact]
public void MappingMatcher_FindBestMatch_WhenNoMappingsDefined_ShouldReturnNull()
{
// Assign
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1");
// Act
var result = _sut.FindBestMatch(request);
// Assert
result.Match.Should().BeNull();
result.Partial.Should().BeNull();
}
[Fact]
public void MappingMatcher_FindBestMatch_WhenMappingThrowsException_ShouldReturnNull()
{
// Assign
var mappingMock = new Mock<IMapping>();
mappingMock.Setup(m => m.GetRequestMatchResult(It.IsAny<RequestMessage>(), It.IsAny<string>())).Throws<Exception>();
var mappings = new ConcurrentDictionary<Guid, IMapping>();
mappings.TryAdd(Guid.NewGuid(), mappingMock.Object);
_optionsMock.Setup(o => o.Mappings).Returns(mappings);
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1");
// Act
var result = _sut.FindBestMatch(request);
// Assert
result.Match.Should().BeNull();
result.Partial.Should().BeNull();
}
[Fact]
public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsFalse_ShouldReturnExactMatch()
{
// Assign
var guid1 = Guid.Parse("00000000-0000-0000-0000-000000000001");
var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002");
var mappings = InitMappings
(
(guid1, new[] { 0.1 }),
(guid2, new[] { 1.0 })
);
_optionsMock.Setup(o => o.Mappings).Returns(mappings);
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1");
// Act
var result = _sut.FindBestMatch(request);
// Assert
result.Match.Should().NotBeNull();
result.Match!.Mapping.Guid.Should().Be(guid2);
result.Match.RequestMatchResult.AverageTotalScore.Should().Be(1.0);
result.Partial.Should().NotBeNull();
result.Partial!.Mapping.Guid.Should().Be(guid2);
result.Partial.RequestMatchResult.AverageTotalScore.Should().Be(1.0);
}
[Fact]
public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsFalse_AndNoExactMatch_ShouldReturnNullExactMatch_And_PartialMatch()
{
// Assign
var guid1 = Guid.Parse("00000000-0000-0000-0000-000000000001");
var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002");
var mappings = InitMappings
(
(guid1, new[] { 0.1 }),
(guid2, new[] { 0.9 })
);
_optionsMock.Setup(o => o.Mappings).Returns(mappings);
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1");
// Act
var result = _sut.FindBestMatch(request);
// Assert
result.Match.Should().BeNull();
result.Partial.Should().NotBeNull();
result.Partial!.Mapping.Guid.Should().Be(guid2);
result.Partial.RequestMatchResult.AverageTotalScore.Should().Be(0.9);
}
[Fact]
public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsTrue_ShouldReturnAnyMatch()
{
// Assign
var guid1 = Guid.Parse("00000000-0000-0000-0000-000000000001");
var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002");
_optionsMock.SetupGet(o => o.AllowPartialMapping).Returns(true);
var mappings = InitMappings(
(guid1, new[] { 0.1 }),
(guid2, new[] { 0.9 })
);
_optionsMock.Setup(o => o.Mappings).Returns(mappings);
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1");
// Act
var result = _sut.FindBestMatch(request);
// Assert
result.Match.Should().NotBeNull();
result.Match!.Mapping.Guid.Should().Be(guid2);
result.Match.RequestMatchResult.AverageTotalScore.Should().Be(0.9);
result.Partial.Should().NotBeNull();
result.Partial!.Mapping.Guid.Should().Be(guid2);
result.Partial.RequestMatchResult.AverageTotalScore.Should().Be(0.9);
}
[Fact]
public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsFalse_And_WithSameAverageScoreButMoreMatchers_ReturnsMatchWithMoreMatchers()
{
// Assign
var guid1 = Guid.Parse("00000000-0000-0000-0000-000000000001");
var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002");
var mappings = InitMappings(
(guid1, new[] { 1.0 }),
(guid2, new[] { 1.0, 1.0 })
);
_optionsMock.Setup(o => o.Mappings).Returns(mappings);
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1");
// Act
var result = _sut.FindBestMatch(request);
// Assert and Verify
result.Match.Should().NotBeNull();
result.Match!.Mapping.Guid.Should().Be(guid2);
result.Match.RequestMatchResult.AverageTotalScore.Should().Be(1.0);
result.Partial.Should().NotBeNull();
result.Partial!.Mapping.Guid.Should().Be(guid2);
result.Partial.RequestMatchResult.AverageTotalScore.Should().Be(1.0);
}
private static ConcurrentDictionary<Guid, IMapping> InitMappings(params (Guid guid, double[] scores)[] matches)
{
var mappings = new ConcurrentDictionary<Guid, IMapping>();
foreach (var match in matches)
{ {
_optionsMock = new Mock<IWireMockMiddlewareOptions>();
_optionsMock.SetupAllProperties();
_optionsMock.Setup(o => o.Mappings).Returns(new ConcurrentDictionary<Guid, IMapping>());
_optionsMock.Setup(o => o.LogEntries).Returns(new ConcurrentObservableCollection<LogEntry>());
_optionsMock.Setup(o => o.Scenarios).Returns(new ConcurrentDictionary<string, ScenarioState>());
var loggerMock = new Mock<IWireMockLogger>();
loggerMock.SetupAllProperties();
loggerMock.Setup(l => l.Error(It.IsAny<string>()));
_optionsMock.Setup(o => o.Logger).Returns(loggerMock.Object);
_sut = new MappingMatcher(_optionsMock.Object);
}
[Fact]
public void MappingMatcher_FindBestMatch_WhenNoMappingsDefined_ShouldReturnNull()
{
// Assign
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1");
// Act
var result = _sut.FindBestMatch(request);
// Assert
result.Match.Should().BeNull();
result.Partial.Should().BeNull();
}
[Fact]
public void MappingMatcher_FindBestMatch_WhenMappingThrowsException_ShouldReturnNull()
{
// Assign
var mappingMock = new Mock<IMapping>(); var mappingMock = new Mock<IMapping>();
mappingMock.Setup(m => m.GetRequestMatchResult(It.IsAny<RequestMessage>(), It.IsAny<string>())).Throws<Exception>(); mappingMock.SetupGet(m => m.Guid).Returns(match.guid);
var mappings = new ConcurrentDictionary<Guid, IMapping>(); var requestMatchResult = new RequestMatchResult();
mappings.TryAdd(Guid.NewGuid(), mappingMock.Object); foreach (var score in match.scores)
_optionsMock.Setup(o => o.Mappings).Returns(mappings);
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1");
// Act
var result = _sut.FindBestMatch(request);
// Assert
result.Match.Should().BeNull();
result.Partial.Should().BeNull();
}
[Fact]
public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsFalse_ShouldReturnExactMatch()
{
// Assign
var guid1 = Guid.Parse("00000000-0000-0000-0000-000000000001");
var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002");
var mappings = InitMappings(
(guid1, new[] { 0.1 }),
(guid2, new[] { 1.0 })
);
_optionsMock.Setup(o => o.Mappings).Returns(mappings);
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1");
// Act
var result = _sut.FindBestMatch(request);
// Assert
result.Match.Mapping.Guid.Should().Be(guid2);
result.Match.RequestMatchResult.AverageTotalScore.Should().Be(1.0);
result.Partial.Mapping.Guid.Should().Be(guid2);
result.Partial.RequestMatchResult.AverageTotalScore.Should().Be(1.0);
}
[Fact]
public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsFalse_AndNoExactmatch_ShouldReturnNullExactMatch_And_PartialMatch()
{
// Assign
var guid1 = Guid.Parse("00000000-0000-0000-0000-000000000001");
var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002");
var mappings = InitMappings(
(guid1, new[] { 0.1 }),
(guid2, new[] { 0.9 })
);
_optionsMock.Setup(o => o.Mappings).Returns(mappings);
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1");
// Act
var result = _sut.FindBestMatch(request);
// Assert
result.Match.Should().BeNull();
result.Partial.Mapping.Guid.Should().Be(guid2);
result.Partial.RequestMatchResult.AverageTotalScore.Should().Be(0.9);
}
[Fact]
public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsTrue_ShouldReturnAnyMatch()
{
// Assign
var guid1 = Guid.Parse("00000000-0000-0000-0000-000000000001");
var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002");
_optionsMock.SetupGet(o => o.AllowPartialMapping).Returns(true);
var mappings = InitMappings(
(guid1, new[] { 0.1 }),
(guid2, new[] { 0.9 })
);
_optionsMock.Setup(o => o.Mappings).Returns(mappings);
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1");
// Act
var result = _sut.FindBestMatch(request);
// Assert
result.Match.Mapping.Guid.Should().Be(guid2);
result.Match.RequestMatchResult.AverageTotalScore.Should().Be(0.9);
result.Partial.Mapping.Guid.Should().Be(guid2);
result.Partial.RequestMatchResult.AverageTotalScore.Should().Be(0.9);
}
[Fact]
public void MappingMatcher_FindBestMatch_WhenAllowPartialMappingIsFalse_And_WithSameAverageScoreButMoreMatchers_ReturnsMatchWithMoreMatchers()
{
// Assign
var guid1 = Guid.Parse("00000000-0000-0000-0000-000000000001");
var guid2 = Guid.Parse("00000000-0000-0000-0000-000000000002");
var mappings = InitMappings(
(guid1, new[] { 1.0 }),
(guid2, new[] { 1.0, 1.0 })
);
_optionsMock.Setup(o => o.Mappings).Returns(mappings);
var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1");
// Act
var result = _sut.FindBestMatch(request);
// Assert and Verify
result.Match.Mapping.Guid.Should().Be(guid2);
result.Match.RequestMatchResult.AverageTotalScore.Should().Be(1.0);
result.Partial.Mapping.Guid.Should().Be(guid2);
result.Partial.RequestMatchResult.AverageTotalScore.Should().Be(1.0);
}
private ConcurrentDictionary<Guid, IMapping> InitMappings(params (Guid guid, double[] scores)[] matches)
{
var mappings = new ConcurrentDictionary<Guid, IMapping>();
foreach (var match in matches)
{ {
var mappingMock = new Mock<IMapping>(); requestMatchResult.AddScore(typeof(object), score);
mappingMock.SetupGet(m => m.Guid).Returns(match.guid);
var requestMatchResult = new RequestMatchResult();
foreach (var score in match.scores)
{
requestMatchResult.AddScore(typeof(object), score);
}
mappingMock.Setup(m => m.GetRequestMatchResult(It.IsAny<RequestMessage>(), It.IsAny<string>())).Returns(requestMatchResult);
mappings.TryAdd(match.guid, mappingMock.Object);
} }
return mappings; mappingMock.Setup(m => m.GetRequestMatchResult(It.IsAny<RequestMessage>(), It.IsAny<string>())).Returns(requestMatchResult);
mappings.TryAdd(match.guid, mappingMock.Object);
} }
return mappings;
} }
} }

View File

@@ -1,48 +1,47 @@
using FluentAssertions; using FluentAssertions;
using Moq; using Moq;
using System; using System;
using WireMock.Handlers; using WireMock.Handlers;
using WireMock.Util; using WireMock.Util;
using Xunit; using Xunit;
namespace WireMock.Net.Tests.Util namespace WireMock.Net.Tests.Util;
public class FileHelperTests
{ {
public class FileHelperTests [Fact]
public void TryReadMappingFileWithRetryAndDelay_WithIFileSystemHandlerOk_ReturnsTrue()
{ {
[Fact] // Assign
public void TryReadMappingFileWithRetryAndDelay_WithIFileSystemHandlerOk_ReturnsTrue() var staticMappingHandlerMock = new Mock<IFileSystemHandler>();
{ staticMappingHandlerMock.Setup(m => m.ReadMappingFile(It.IsAny<string>())).Returns("text");
// Assign
var staticMappingHandlerMock = new Mock<IFileSystemHandler>();
staticMappingHandlerMock.Setup(m => m.ReadMappingFile(It.IsAny<string>())).Returns("text");
// Act // Act
bool result = FileHelper.TryReadMappingFileWithRetryAndDelay(staticMappingHandlerMock.Object, @"c:\temp", out string value); bool result = FileHelper.TryReadMappingFileWithRetryAndDelay(staticMappingHandlerMock.Object, @"c:\temp", out var value);
// Assert // Assert
result.Should().BeTrue(); result.Should().BeTrue();
value.Should().Be("text"); value.Should().Be("text");
// Verify // Verify
staticMappingHandlerMock.Verify(m => m.ReadMappingFile(@"c:\temp"), Times.Once); staticMappingHandlerMock.Verify(m => m.ReadMappingFile(@"c:\temp"), Times.Once);
}
[Fact]
public void TryReadMappingFileWithRetryAndDelay_WithIFileSystemHandlerThrows_ReturnsFalse()
{
// Assign
var staticMappingHandlerMock = new Mock<IFileSystemHandler>();
staticMappingHandlerMock.Setup(m => m.ReadMappingFile(It.IsAny<string>())).Throws<NotSupportedException>();
// Act
bool result = FileHelper.TryReadMappingFileWithRetryAndDelay(staticMappingHandlerMock.Object, @"c:\temp", out string value);
// Assert
result.Should().BeFalse();
value.Should().BeNull();
// Verify
staticMappingHandlerMock.Verify(m => m.ReadMappingFile(@"c:\temp"), Times.Exactly(3));
}
} }
}
[Fact]
public void TryReadMappingFileWithRetryAndDelay_WithIFileSystemHandlerThrows_ReturnsFalse()
{
// Assign
var staticMappingHandlerMock = new Mock<IFileSystemHandler>();
staticMappingHandlerMock.Setup(m => m.ReadMappingFile(It.IsAny<string>())).Throws<NotSupportedException>();
// Act
bool result = FileHelper.TryReadMappingFileWithRetryAndDelay(staticMappingHandlerMock.Object, @"c:\temp", out var value);
// Assert
result.Should().BeFalse();
value.Should().BeNull();
// Verify
staticMappingHandlerMock.Verify(m => m.ReadMappingFile(@"c:\temp"), Times.Exactly(3));
}
}

File diff suppressed because it is too large Load Diff