SaveUnmatchedRequests (#703)

This commit is contained in:
Stef Heyenrath
2021-12-24 09:34:26 +01:00
committed by GitHub
parent 3e1c3598f7
commit 1d1ff4a418
18 changed files with 180 additions and 85 deletions

View File

@@ -1,4 +1,7 @@
namespace WireMock.Admin.Settings
using System.Text.RegularExpressions;
using WireMock.Handlers;
namespace WireMock.Admin.Settings
{
/// <summary>
/// Settings
@@ -40,5 +43,15 @@
/// Throw an exception when the Matcher fails because of invalid input. (default set to false).
/// </summary>
public bool? ThrowExceptionWhenMatcherFails { get; set; }
/// <summary>
/// Use the RegexExtended instead of the default <see cref="Regex"/>. (default set to true).
/// </summary>
public bool? UseRegexExtended { get; set; }
/// <summary>
/// Save unmatched requests to a file using the <see cref="IFileSystemHandler"/>. (default set to false).
/// </summary>
public bool? SaveUnmatchedRequests { get; set; }
}
}

View File

@@ -96,5 +96,18 @@ namespace WireMock.Handlers
/// <param name="filename">The filename.</param>
/// <returns>The file content as a string.</returns>
string ReadFileAsString([NotNull] string filename);
/// <summary>
/// Gets the folder where the unmatched requests should be stored. For local file system, this would be `{CurrentFolder}/requests/unmatched`.
/// </summary>
/// <returns>The folder name.</returns>
string GetUnmatchedRequestsFolder();
/// <summary>
/// Write a unmatched request to the Unmatched RequestsFolder.
/// </summary>
/// <param name="filename">The filename.</param>
/// <param name="text">The text.</param>
void WriteUnmatchedRequest([NotNull] string filename, [NotNull] string text);
}
}

View File

@@ -11,6 +11,7 @@ namespace WireMock.Handlers
public class LocalFileSystemHandler : IFileSystemHandler
{
private static readonly string AdminMappingsFolder = Path.Combine("__admin", "mappings");
private static readonly string UnmatchedRequestsFolder = Path.Combine("requests", "unmatched");
private readonly string _rootFolder;
@@ -31,7 +32,7 @@ namespace WireMock.Handlers
}
/// <inheritdoc cref="IFileSystemHandler.FolderExists"/>
public bool FolderExists(string path)
public virtual bool FolderExists(string path)
{
Check.NotNullOrEmpty(path, nameof(path));
@@ -39,7 +40,7 @@ namespace WireMock.Handlers
}
/// <inheritdoc cref="IFileSystemHandler.CreateFolder"/>
public void CreateFolder(string path)
public virtual void CreateFolder(string path)
{
Check.NotNullOrEmpty(path, nameof(path));
@@ -47,7 +48,7 @@ namespace WireMock.Handlers
}
/// <inheritdoc cref="IFileSystemHandler.EnumerateFiles"/>
public IEnumerable<string> EnumerateFiles(string path, bool includeSubdirectories)
public virtual IEnumerable<string> EnumerateFiles(string path, bool includeSubdirectories)
{
Check.NotNullOrEmpty(path, nameof(path));
@@ -55,13 +56,13 @@ namespace WireMock.Handlers
}
/// <inheritdoc cref="IFileSystemHandler.GetMappingFolder"/>
public string GetMappingFolder()
public virtual string GetMappingFolder()
{
return Path.Combine(_rootFolder, AdminMappingsFolder);
}
/// <inheritdoc cref="IFileSystemHandler.ReadMappingFile"/>
public string ReadMappingFile(string path)
public virtual string ReadMappingFile(string path)
{
Check.NotNullOrEmpty(path, nameof(path));
@@ -69,7 +70,7 @@ namespace WireMock.Handlers
}
/// <inheritdoc cref="IFileSystemHandler.WriteMappingFile(string, string)"/>
public void WriteMappingFile(string path, string text)
public virtual void WriteMappingFile(string path, string text)
{
Check.NotNullOrEmpty(path, nameof(path));
Check.NotNull(text, nameof(text));
@@ -78,7 +79,7 @@ namespace WireMock.Handlers
}
/// <inheritdoc cref="IFileSystemHandler.ReadResponseBodyAsFile"/>
public byte[] ReadResponseBodyAsFile(string path)
public virtual byte[] ReadResponseBodyAsFile(string path)
{
Check.NotNullOrEmpty(path, nameof(path));
path = PathUtils.CleanPath(path);
@@ -88,7 +89,7 @@ namespace WireMock.Handlers
}
/// <inheritdoc cref="IFileSystemHandler.ReadResponseBodyAsString"/>
public string ReadResponseBodyAsString(string path)
public virtual string ReadResponseBodyAsString(string path)
{
Check.NotNullOrEmpty(path, nameof(path));
path = PathUtils.CleanPath(path);
@@ -98,42 +99,63 @@ namespace WireMock.Handlers
}
/// <inheritdoc cref="IFileSystemHandler.FileExists"/>
public bool FileExists(string filename)
public virtual bool FileExists(string filename)
{
Check.NotNullOrEmpty(filename, nameof(filename));
return File.Exists(AdjustPath(filename));
return File.Exists(AdjustPathForMappingFolder(filename));
}
/// <inheritdoc cref="IFileSystemHandler.WriteFile(string, byte[])"/>
public void WriteFile(string filename, byte[] bytes)
public virtual void WriteFile(string filename, byte[] bytes)
{
Check.NotNullOrEmpty(filename, nameof(filename));
Check.NotNull(bytes, nameof(bytes));
File.WriteAllBytes(AdjustPath(filename), bytes);
File.WriteAllBytes(AdjustPathForMappingFolder(filename), bytes);
}
/// <inheritdoc cref="IFileSystemHandler.DeleteFile"/>
public void DeleteFile(string filename)
public virtual void DeleteFile(string filename)
{
Check.NotNullOrEmpty(filename, nameof(filename));
File.Delete(AdjustPath(filename));
File.Delete(AdjustPathForMappingFolder(filename));
}
/// <inheritdoc cref="IFileSystemHandler.ReadFile"/>
public byte[] ReadFile(string filename)
public virtual byte[] ReadFile(string filename)
{
Check.NotNullOrEmpty(filename, nameof(filename));
return File.ReadAllBytes(AdjustPath(filename));
return File.ReadAllBytes(AdjustPathForMappingFolder(filename));
}
/// <inheritdoc cref="IFileSystemHandler.ReadFileAsString"/>
public string ReadFileAsString(string filename)
public virtual string ReadFileAsString(string filename)
{
return File.ReadAllText(AdjustPath(Check.NotNullOrEmpty(filename, nameof(filename))));
return File.ReadAllText(AdjustPathForMappingFolder(Check.NotNullOrEmpty(filename, nameof(filename))));
}
/// <inheritdoc cref="IFileSystemHandler.GetUnmatchedRequestsFolder"/>
public virtual string GetUnmatchedRequestsFolder()
{
return Path.Combine(_rootFolder, UnmatchedRequestsFolder);
}
/// <inheritdoc cref="IFileSystemHandler.WriteUnmatchedRequest"/>
public virtual void WriteUnmatchedRequest(string filename, string text)
{
Check.NotNullOrEmpty(filename, nameof(filename));
Check.NotNull(text, nameof(text));
var folder = GetUnmatchedRequestsFolder();
if (!FolderExists(folder))
{
CreateFolder(folder);
}
File.WriteAllText(Path.Combine(folder, filename), text);
}
/// <summary>
@@ -141,7 +163,7 @@ namespace WireMock.Handlers
/// </summary>
/// <param name="filename">The path.</param>
/// <returns>Adjusted path</returns>
private string AdjustPath(string filename)
private string AdjustPathForMappingFolder(string filename)
{
return Path.Combine(GetMappingFolder(), filename);
}

View File

@@ -4,6 +4,7 @@ using WireMock.Handlers;
using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Util;
using JetBrains.Annotations;
#if !USE_ASPNETCORE
using Owin;
#else
@@ -64,5 +65,7 @@ namespace WireMock.Owin
string X509CertificatePassword { get; set; }
bool CustomCertificateDefined { get; }
bool? SaveUnmatchedRequests { get; set; }
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -23,7 +23,7 @@ namespace WireMock.Owin.Mappers
/// <inheritdoc cref="IOwinRequestMapper.MapAsync"/>
public async Task<RequestMessage> MapAsync(IRequest request, IWireMockMiddlewareOptions options)
{
(UrlDetails urldetails, string clientIP) = ParseRequest(request);
var (urlDetails, clientIP) = ParseRequest(request);
string method = request.Method;
@@ -68,7 +68,7 @@ namespace WireMock.Owin.Mappers
body = await BodyParser.Parse(bodyParserSettings);
}
return new RequestMessage(urldetails, method, clientIP, body, headers, cookies) { DateTime = DateTime.UtcNow };
return new RequestMessage(urlDetails, method, clientIP, body, headers, cookies) { DateTime = DateTime.UtcNow };
}
private (UrlDetails UrlDetails, string ClientIP) ParseRequest(IRequest request)

View File

@@ -2,6 +2,7 @@ using System;
using System.Threading.Tasks;
using WireMock.Logging;
using System.Linq;
using HandlebarsDotNet.Helpers.Utils;
using WireMock.Matchers;
using WireMock.Http;
using WireMock.Owin.Mappers;
@@ -35,28 +36,18 @@ namespace WireMock.Owin
#if !USE_ASPNETCORE
public WireMockMiddleware(Next next, IWireMockMiddlewareOptions options, IOwinRequestMapper requestMapper, IOwinResponseMapper responseMapper, IMappingMatcher mappingMatcher) : base(next)
{
Check.NotNull(options, nameof(options));
Check.NotNull(requestMapper, nameof(requestMapper));
Check.NotNull(responseMapper, nameof(responseMapper));
Check.NotNull(mappingMatcher, nameof(mappingMatcher));
_options = options;
_requestMapper = requestMapper;
_responseMapper = responseMapper;
_mappingMatcher = mappingMatcher;
_options = Check.NotNull(options, nameof(options));
_requestMapper = Check.NotNull(requestMapper, nameof(requestMapper));
_responseMapper = Check.NotNull(responseMapper, nameof(responseMapper));
_mappingMatcher = Check.NotNull(mappingMatcher, nameof(mappingMatcher));
}
#else
public WireMockMiddleware(Next next, IWireMockMiddlewareOptions options, IOwinRequestMapper requestMapper, IOwinResponseMapper responseMapper, IMappingMatcher mappingMatcher)
{
Check.NotNull(options, nameof(options));
Check.NotNull(requestMapper, nameof(requestMapper));
Check.NotNull(responseMapper, nameof(responseMapper));
Check.NotNull(mappingMatcher, nameof(mappingMatcher));
_options = options;
_requestMapper = requestMapper;
_responseMapper = responseMapper;
_mappingMatcher = mappingMatcher;
_options = Check.NotNull(options, nameof(options));
_requestMapper = Check.NotNull(requestMapper, nameof(requestMapper));
_responseMapper = Check.NotNull(responseMapper, nameof(responseMapper));
_mappingMatcher = Check.NotNull(mappingMatcher, nameof(mappingMatcher));
}
#endif
@@ -70,20 +61,18 @@ namespace WireMock.Owin
{
lock (_lock)
{
return InvokeInternal(ctx);
return InvokeInternalAsync(ctx);
}
}
else
{
return InvokeInternal(ctx);
}
return InvokeInternalAsync(ctx);
}
private async Task InvokeInternal(IContext ctx)
private async Task InvokeInternalAsync(IContext ctx)
{
var request = await _requestMapper.MapAsync(ctx.Request, _options);
bool logRequest = false;
var logRequest = false;
ResponseMessage response = null;
(MappingMatcherResult Match, MappingMatcherResult Partial) result = (null, null);
try
@@ -185,6 +174,19 @@ namespace WireMock.Owin
LogRequest(log, logRequest);
try
{
if (_options.SaveUnmatchedRequests == true && result.Match?.RequestMatchResult.IsPerfectMatch != true)
{
var filename = $"{log.Guid}.LogEntry.json";
_options.FileSystemHandler?.WriteUnmatchedRequest(filename, Util.JsonUtils.Serialize(log));
}
}
catch
{
// Empty catch
}
await _responseMapper.MapAsync(response, ctx.Response);
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using WireMock.Handlers;
using WireMock.Logging;
@@ -78,5 +78,8 @@ namespace WireMock.Owin
public bool CustomCertificateDefined =>
!string.IsNullOrEmpty(X509StoreName) && !string.IsNullOrEmpty(X509StoreLocation) ||
!string.IsNullOrEmpty(X509CertificateFilePath) && !string.IsNullOrEmpty(X509CertificatePassword);
/// <inheritdoc cref="IWireMockMiddlewareOptions.SaveUnmatchedRequests"/>
public bool? SaveUnmatchedRequests { get; set; }
}
}

View File

@@ -1,4 +1,4 @@
using Newtonsoft.Json;
using Newtonsoft.Json;
namespace WireMock.Serialization
{
@@ -15,5 +15,10 @@ namespace WireMock.Serialization
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Include
};
public static readonly JsonSerializerSettings JsonDeserializerSettingsWithDateParsingNone = new JsonSerializerSettings
{
DateParseHandling = DateParseHandling.None
};
}
}

View File

@@ -291,7 +291,9 @@ namespace WireMock.Server
GlobalProcessingDelay = (int?)_options.RequestProcessingDelay?.TotalMilliseconds,
AllowBodyForAllHttpMethods = _settings.AllowBodyForAllHttpMethods,
HandleRequestsSynchronously = _settings.HandleRequestsSynchronously,
ThrowExceptionWhenMatcherFails = _settings.ThrowExceptionWhenMatcherFails
ThrowExceptionWhenMatcherFails = _settings.ThrowExceptionWhenMatcherFails,
UseRegexExtended = _settings.UseRegexExtended,
SaveUnmatchedRequests = _settings.SaveUnmatchedRequests
};
return ToJson(model);
@@ -302,31 +304,16 @@ namespace WireMock.Server
var settings = DeserializeObject<SettingsModel>(requestMessage);
_options.MaxRequestLogCount = settings.MaxRequestLogCount;
_options.RequestLogExpirationDuration = settings.RequestLogExpirationDuration;
if (settings.AllowPartialMapping != null)
{
_options.AllowPartialMapping = settings.AllowPartialMapping.Value;
}
_options.AllowPartialMapping = settings.AllowPartialMapping;
if (settings.GlobalProcessingDelay != null)
{
_options.RequestProcessingDelay = TimeSpan.FromMilliseconds(settings.GlobalProcessingDelay.Value);
}
if (settings.AllowBodyForAllHttpMethods != null)
{
_options.AllowBodyForAllHttpMethods = settings.AllowBodyForAllHttpMethods.Value;
}
if (settings.HandleRequestsSynchronously != null)
{
_options.HandleRequestsSynchronously = settings.HandleRequestsSynchronously.Value;
}
if (settings.ThrowExceptionWhenMatcherFails != null)
{
_settings.ThrowExceptionWhenMatcherFails = settings.ThrowExceptionWhenMatcherFails.Value;
}
_options.AllowBodyForAllHttpMethods = settings.AllowBodyForAllHttpMethods;
_options.HandleRequestsSynchronously = settings.HandleRequestsSynchronously;
_settings.ThrowExceptionWhenMatcherFails = settings.ThrowExceptionWhenMatcherFails;
_settings.UseRegexExtended = settings.UseRegexExtended;
_settings.SaveUnmatchedRequests = settings.SaveUnmatchedRequests;
return ResponseMessageBuilder.Create("Settings updated");
}

View File

@@ -226,6 +226,7 @@ namespace WireMock.Server
_options.Logger = _settings.Logger;
_options.DisableJsonBodyParsing = _settings.DisableJsonBodyParsing;
_options.HandleRequestsSynchronously = settings.HandleRequestsSynchronously;
_options.SaveUnmatchedRequests = settings.SaveUnmatchedRequests;
if (settings.CustomCertificateDefined)
{

View File

@@ -213,15 +213,21 @@ namespace WireMock.Settings
bool CustomCertificateDefined { get; }
/// <summary>
/// Defines the global IWebhookSettingsto use
/// Defines the global IWebhookSettings to use.
/// </summary>
[PublicAPI]
IWebhookSettings WebhookSettings { get; set; }
/// <summary>
/// Use the <see cref="RegexExtended"/> instead of the default <see cref="Regex"/>.
/// Use the <see cref="RegexExtended"/> instead of the default <see cref="Regex"/> (default set to true).
/// </summary>
[PublicAPI]
bool? UseRegexExtended { get; }
bool? UseRegexExtended { get; set; }
/// <summary>
/// Save unmatched requests to a file using the <see cref="IFileSystemHandler"/> (default set to false).
/// </summary>
[PublicAPI]
bool? SaveUnmatchedRequests { get; set; }
}
}

View File

@@ -20,7 +20,6 @@ namespace WireMock.Settings
public int? Port { get; set; }
/// <inheritdoc cref="IWireMockServerSettings.UseSSL"/>
// ReSharper disable once InconsistentNaming
[PublicAPI]
public bool? UseSSL { get; set; }
@@ -155,5 +154,9 @@ namespace WireMock.Settings
/// <inheritdoc cref="IWireMockServerSettings.UseRegexExtended"/>
[PublicAPI]
public bool? UseRegexExtended { get; set; } = true;
/// <inheritdoc cref="IWireMockServerSettings.SaveUnmatchedRequests"/>
[PublicAPI]
public bool? SaveUnmatchedRequests { get; set; }
}
}

View File

@@ -49,7 +49,8 @@ namespace WireMock.Settings
DisableJsonBodyParsing = parser.GetBoolValue("DisableJsonBodyParsing"),
HandleRequestsSynchronously = parser.GetBoolValue("HandleRequestsSynchronously"),
ThrowExceptionWhenMatcherFails = parser.GetBoolValue("ThrowExceptionWhenMatcherFails"),
UseRegexExtended = parser.GetBoolValue(nameof(IWireMockServerSettings.UseRegexExtended), true)
UseRegexExtended = parser.GetBoolValue(nameof(IWireMockServerSettings.UseRegexExtended), true),
SaveUnmatchedRequests = parser.GetBoolValue(nameof(IWireMockServerSettings.SaveUnmatchedRequests))
};
if (logger != null)

View File

@@ -1,18 +1,19 @@
using Newtonsoft.Json;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WireMock.Serialization;
namespace WireMock.Util
{
internal static class JsonUtils
{
private static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings
public static string Serialize<T>(T value)
{
DateParseHandling = DateParseHandling.None
};
return JsonConvert.SerializeObject(value, JsonSerializationConstants.JsonSerializerSettingsIncludeNullValues);
}
/// <summary>
/// Load a Newtonsoft.Json.Linq.JObject from a string that contains JSON.
@@ -22,7 +23,7 @@ namespace WireMock.Util
/// <returns>A Newtonsoft.Json.Linq.JToken populated from the string that contains JSON.</returns>
public static JToken Parse(string json)
{
return JsonConvert.DeserializeObject<JToken>(json, JsonSerializerSettings);
return JsonConvert.DeserializeObject<JToken>(json, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone);
}
/// <summary>
@@ -33,7 +34,7 @@ namespace WireMock.Util
/// <returns>The deserialized object from the JSON string.</returns>
public static object DeserializeObject(string json)
{
return JsonConvert.DeserializeObject(json, JsonSerializerSettings);
return JsonConvert.DeserializeObject(json, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone);
}
/// <summary>
@@ -44,7 +45,7 @@ namespace WireMock.Util
/// <returns>The deserialized object from the JSON string.</returns>
public static T DeserializeObject<T>(string json)
{
return JsonConvert.DeserializeObject<T>(json, JsonSerializerSettings);
return JsonConvert.DeserializeObject<T>(json, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone);
}
public static T ParseJTokenToObject<T>(object value)