Add RequestLogExpirationDuration and MaxRequestLogCount (#43) (#45)

This commit is contained in:
Stef Heyenrath
2017-08-20 11:20:35 +02:00
committed by GitHub
parent 76f0e04874
commit f8ad2cd3d6
19 changed files with 682 additions and 509 deletions

22
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,22 @@
{
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (WireMock.Net.StandAlone.NETCoreApp)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build_WireMock.Net.StandAlone.NETCoreApp",
"program": "${workspaceRoot}/examples/WireMock.Net.StandAlone.NETCoreApp/bin/Debug/netcoreapp2.0/WireMock.Net.StandAlone.NETCoreApp.dll",
"args": [],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"console": "internalConsole"
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickProcess}"
}
]
}

17
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,17 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"taskName": "build_WireMock.Net.StandAlone.NETCoreApp",
"command": "dotnet build ${workspaceRoot}/examples/WireMock.Net.StandAlone.NETCoreApp/WireMock.Net.StandAlone.NETCoreApp.csproj -f netcoreapp2.0 ",
"type": "shell",
"group": "build",
"presentation": {
"reveal": "silent"
},
"problemMatcher": "$msCompile"
}
]
}

View File

@@ -12,7 +12,7 @@ namespace WireMock.Net.Client
static void Main(string[] args)
{
// Create an implementation of the IFluentMockServerAdmin and pass in the base URL for the API.
var api = RestClient.For<IFluentMockServerAdmin>("http://localhost:9090");
var api = RestClient.For<IFluentMockServerAdmin>("http://localhost:9091");
// Set BASIC Auth
var value = Convert.ToBase64String(Encoding.ASCII.GetBytes("a:b"));

View File

@@ -12,8 +12,7 @@
<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
<!--<ProjectReference Include="..\WireMock.Net\WireMock.Net.csproj" />-->
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.1" />
</ItemGroup>
</Project>

View File

@@ -13,8 +13,8 @@ namespace WireMock.Net.ConsoleApplication
{
public static void Run()
{
string url1 = "http://localhost:9090/";
string url2 = "http://localhost:9091/";
string url1 = "http://localhost:9091/";
string url2 = "http://localhost:9092/";
string url3 = "https://localhost:9443/";
var server = FluentMockServer.Start(new FluentMockServerSettings

View File

@@ -1,15 +1,42 @@
using System;
using System.Threading;
using WireMock.Server;
namespace WireMock.Net.StandAlone.NETCoreApp
{
class Program
{
private static int sleepTime = 30000;
private static FluentMockServer server;
static void Main(string[] args)
{
StandAloneApp.Start(args);
server = StandAloneApp.Start(args);
Console.WriteLine("Press any key to stop the server");
Console.ReadKey();
Console.WriteLine($"{DateTime.UtcNow} Press Ctrl+C to shut down");
System.Console.CancelKeyPress += (s,e) =>
{
Stop("CancelKeyPress");
};
System.Runtime.Loader.AssemblyLoadContext.Default.Unloading += ctx =>
{
Stop("AssemblyLoadContext.Default.Unloading");
};
while(true)
{
Console.WriteLine($"{DateTime.UtcNow} WireMock.Net server running");
Thread.Sleep(sleepTime);
}
}
private static void Stop(string why)
{
Console.WriteLine($"{DateTime.UtcNow} WireMock.Net server stopping because '{why}'");
server.Stop();
Console.WriteLine($"{DateTime.UtcNow} WireMock.Net server stopped");
}
}
}

View File

@@ -2,7 +2,7 @@
"profiles": {
"WireMock.Net.StandAlone.NETCoreApp": {
"commandName": "Project",
"commandLineArgs": "--Urls http://*:9090"
"commandLineArgs": "--Urls http://*:9091"
}
}
}

View File

@@ -2,8 +2,8 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.0</TargetFramework>
<RuntimeFrameworkVersion>1.0.1</RuntimeFrameworkVersion>
<TargetFrameworks>netcoreapp1.0;netcoreapp2.0</TargetFrameworks>
<!-- <RuntimeFrameworkVersion>1.0.1</RuntimeFrameworkVersion> -->
<ApplicationIcon>../../WireMock.Net-Logo.ico</ApplicationIcon>
</PropertyGroup>

View File

@@ -43,6 +43,12 @@ namespace WireMock.Net.StandAlone
[ValueArgument(typeof(string), "AdminPassword", Description = "The password needed for __admin access.", Optional = true)]
public string AdminPassword { get; set; }
[ValueArgument(typeof(int?), "RequestLogExpirationDuration", Description = "The RequestLog expiration in hours (optional).", Optional = true)]
public int? RequestLogExpirationDuration { get; set; }
[ValueArgument(typeof(int?), "MaxRequestLogCount", Description = "The MaxRequestLog count (optional).", Optional = true)]
public int? MaxRequestLogCount { get; set; }
}
/// <summary>
@@ -76,7 +82,7 @@ namespace WireMock.Net.StandAlone
if (!options.Urls.Any())
{
options.Urls.Add("http://localhost:9090/");
options.Urls.Add("http://localhost:9091/");
}
var settings = new FluentMockServerSettings
@@ -86,7 +92,9 @@ namespace WireMock.Net.StandAlone
ReadStaticMappings = options.ReadStaticMappings,
AllowPartialMapping = options.AllowPartialMapping,
AdminUsername = options.AdminUsername,
AdminPassword = options.AdminPassword
AdminPassword = options.AdminPassword,
RequestLogExpirationDuration = options.RequestLogExpirationDuration,
MaxRequestLogCount = options.MaxRequestLogCount
};
if (!string.IsNullOrEmpty(options.ProxyURL))

View File

@@ -18,7 +18,7 @@
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/WireMock-Net/WireMock.Net</RepositoryUrl>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<DebugType>full</DebugType>
<DebugType>portable</DebugType>
<ApplicationIcon>../../WireMock.Net-Logo.ico</ApplicationIcon>
<RootNamespace>WireMock.Net.StandAlone</RootNamespace>
</PropertyGroup>

View File

@@ -14,5 +14,15 @@
/// Gets or sets if partial mapping is allowed.
/// </summary>
public bool? AllowPartialMapping { get; set; }
/// <summary>
/// Gets or sets the RequestLog expiration in hours
/// </summary>
public int? RequestLogExpirationDuration { get; set; }
/// <summary>
/// Gets or sets the MaxRequestLog count.
/// </summary>
public int? MaxRequestLogCount { get; set; }
}
}

View File

@@ -1,143 +1,157 @@
using System;
using System.Collections;
using System.Threading.Tasks;
using WireMock.Logging;
using WireMock.Matchers.Request;
using System.Linq;
#if !NETSTANDARD
using Microsoft.Owin;
#else
using Microsoft.AspNetCore.Http;
#endif
namespace WireMock.Owin
{
#if !NETSTANDARD
internal class WireMockMiddleware : OwinMiddleware
#else
internal class WireMockMiddleware
#endif
{
private static readonly Task CompletedTask = Task.FromResult(false);
private readonly WireMockMiddlewareOptions _options;
private readonly OwinRequestMapper _requestMapper = new OwinRequestMapper();
private readonly OwinResponseMapper _responseMapper = new OwinResponseMapper();
#if !NETSTANDARD
public WireMockMiddleware(OwinMiddleware next, WireMockMiddlewareOptions options) : base(next)
{
_options = options;
}
#else
public WireMockMiddleware(RequestDelegate next, WireMockMiddlewareOptions options)
{
_options = options;
}
#endif
#if !NETSTANDARD
public override async Task Invoke(IOwinContext ctx)
#else
public async Task Invoke(HttpContext ctx)
#endif
{
var request = await _requestMapper.MapAsync(ctx.Request);
ResponseMessage response = null;
Mapping targetMapping = null;
RequestMatchResult requestMatchResult = null;
try
{
var mappings = _options.Mappings
.Select(m => new
{
Mapping = m,
MatchResult = m.IsRequestHandled(request)
})
.ToList();
if (_options.AllowPartialMapping)
{
var partialMappings = mappings
.Where(pm => pm.Mapping.IsAdminInterface && pm.MatchResult.IsPerfectMatch || !pm.Mapping.IsAdminInterface)
.OrderBy(m => m.MatchResult)
.ThenBy(m => m.Mapping.Priority)
.ToList();
var bestPartialMatch = partialMappings.FirstOrDefault(pm => pm.MatchResult.AverageTotalScore > 0.0);
targetMapping = bestPartialMatch?.Mapping;
requestMatchResult = bestPartialMatch?.MatchResult;
}
else
{
var perfectMatch = mappings
.OrderBy(m => m.Mapping.Priority)
.FirstOrDefault(m => m.MatchResult.IsPerfectMatch);
targetMapping = perfectMatch?.Mapping;
requestMatchResult = perfectMatch?.MatchResult;
}
if (targetMapping == null)
{
response = new ResponseMessage { StatusCode = 404, Body = "No matching mapping found" };
return;
}
if (targetMapping.IsAdminInterface && _options.AuthorizationMatcher != null)
{
string authorization;
bool present = request.Headers.TryGetValue("Authorization", out authorization);
if (!present || _options.AuthorizationMatcher.IsMatch(authorization) < 1.0)
{
response = new ResponseMessage { StatusCode = 401 };
return;
}
}
if (!targetMapping.IsAdminInterface && _options.RequestProcessingDelay > TimeSpan.Zero)
{
await Task.Delay(_options.RequestProcessingDelay.Value);
}
response = await targetMapping.ResponseToAsync(request);
}
catch (Exception ex)
{
response = new ResponseMessage { StatusCode = 500, Body = ex.ToString() };
}
finally
{
var log = new LogEntry
{
Guid = Guid.NewGuid(),
RequestMessage = request,
ResponseMessage = response,
MappingGuid = targetMapping?.Guid,
MappingTitle = targetMapping?.Title,
RequestMatchResult = requestMatchResult
};
LogRequest(log);
await _responseMapper.MapAsync(response, ctx.Response);
}
await CompletedTask;
}
/// <summary>
/// The log request.
/// </summary>
/// <param name="entry">The request.</param>
private void LogRequest(LogEntry entry)
{
lock (((ICollection)_options.LogEntries).SyncRoot)
{
_options.LogEntries.Add(entry);
}
}
}
using System;
using System.Collections;
using System.Threading.Tasks;
using WireMock.Logging;
using WireMock.Matchers.Request;
using System.Linq;
#if !NETSTANDARD
using Microsoft.Owin;
#else
using Microsoft.AspNetCore.Http;
#endif
namespace WireMock.Owin
{
#if !NETSTANDARD
internal class WireMockMiddleware : OwinMiddleware
#else
internal class WireMockMiddleware
#endif
{
private static readonly Task CompletedTask = Task.FromResult(false);
private readonly WireMockMiddlewareOptions _options;
private readonly OwinRequestMapper _requestMapper = new OwinRequestMapper();
private readonly OwinResponseMapper _responseMapper = new OwinResponseMapper();
#if !NETSTANDARD
public WireMockMiddleware(OwinMiddleware next, WireMockMiddlewareOptions options) : base(next)
{
_options = options;
}
#else
public WireMockMiddleware(RequestDelegate next, WireMockMiddlewareOptions options)
{
_options = options;
}
#endif
#if !NETSTANDARD
public override async Task Invoke(IOwinContext ctx)
#else
public async Task Invoke(HttpContext ctx)
#endif
{
var request = await _requestMapper.MapAsync(ctx.Request);
bool logRequest = false;
ResponseMessage response = null;
Mapping targetMapping = null;
RequestMatchResult requestMatchResult = null;
try
{
var mappings = _options.Mappings
.Select(m => new
{
Mapping = m,
MatchResult = m.IsRequestHandled(request)
})
.ToList();
if (_options.AllowPartialMapping)
{
var partialMappings = mappings
.Where(pm => pm.Mapping.IsAdminInterface && pm.MatchResult.IsPerfectMatch || !pm.Mapping.IsAdminInterface)
.OrderBy(m => m.MatchResult)
.ThenBy(m => m.Mapping.Priority)
.ToList();
var bestPartialMatch = partialMappings.FirstOrDefault(pm => pm.MatchResult.AverageTotalScore > 0.0);
targetMapping = bestPartialMatch?.Mapping;
requestMatchResult = bestPartialMatch?.MatchResult;
}
else
{
var perfectMatch = mappings
.OrderBy(m => m.Mapping.Priority)
.FirstOrDefault(m => m.MatchResult.IsPerfectMatch);
targetMapping = perfectMatch?.Mapping;
requestMatchResult = perfectMatch?.MatchResult;
}
if (targetMapping == null)
{
logRequest = true;
response = new ResponseMessage { StatusCode = 404, Body = "No matching mapping found" };
return;
}
logRequest = !targetMapping.IsAdminInterface;
if (targetMapping.IsAdminInterface && _options.AuthorizationMatcher != null)
{
string authorization;
bool present = request.Headers.TryGetValue("Authorization", out authorization);
if (!present || _options.AuthorizationMatcher.IsMatch(authorization) < 1.0)
{
response = new ResponseMessage { StatusCode = 401 };
return;
}
}
if (!targetMapping.IsAdminInterface && _options.RequestProcessingDelay > TimeSpan.Zero)
{
await Task.Delay(_options.RequestProcessingDelay.Value);
}
response = await targetMapping.ResponseToAsync(request);
}
catch (Exception ex)
{
response = new ResponseMessage { StatusCode = 500, Body = ex.ToString() };
}
finally
{
var log = new LogEntry
{
Guid = Guid.NewGuid(),
RequestMessage = request,
ResponseMessage = response,
MappingGuid = targetMapping?.Guid,
MappingTitle = targetMapping?.Title,
RequestMatchResult = requestMatchResult
};
LogRequest(log, logRequest);
await _responseMapper.MapAsync(response, ctx.Response);
}
await CompletedTask;
}
private void LogRequest(LogEntry entry, bool addRequest)
{
lock (((ICollection)_options.LogEntries).SyncRoot)
{
if (addRequest)
{
_options.LogEntries.Add(entry);
}
if (_options.MaxRequestLogCount != null)
{
_options.LogEntries = _options.LogEntries.Skip(_options.LogEntries.Count - _options.MaxRequestLogCount.Value).ToList();
}
if (_options.RequestLogExpirationDuration != null)
{
var checkTime = DateTime.Now.AddHours(-_options.RequestLogExpirationDuration.Value);
_options.LogEntries = _options.LogEntries.Where(le => le.RequestMessage.DateTime > checkTime).ToList();
}
}
}
}
}

View File

@@ -16,6 +16,10 @@ namespace WireMock.Owin
public IList<Mapping> Mappings { get; set; }
public IList<LogEntry> LogEntries { get; set; }
public int? RequestLogExpirationDuration { get; set; }
public int? MaxRequestLogCount { get; set; }
public WireMockMiddlewareOptions()
{

View File

@@ -161,6 +161,8 @@ namespace WireMock.Server
var model = new SettingsModel
{
AllowPartialMapping = _options.AllowPartialMapping,
MaxRequestLogCount = _options.MaxRequestLogCount,
RequestLogExpirationDuration = _options.RequestLogExpirationDuration,
GlobalProcessingDelay = (int?)_options.RequestProcessingDelay?.TotalMilliseconds
};
@@ -174,6 +176,10 @@ namespace WireMock.Server
if (settings.AllowPartialMapping != null)
_options.AllowPartialMapping = settings.AllowPartialMapping.Value;
_options.MaxRequestLogCount = settings.MaxRequestLogCount;
_options.RequestLogExpirationDuration = settings.RequestLogExpirationDuration;
if (settings.GlobalProcessingDelay != null)
_options.RequestProcessingDelay = TimeSpan.FromMilliseconds(settings.GlobalProcessingDelay.Value);

View File

@@ -1,347 +1,381 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using JetBrains.Annotations;
using WireMock.Http;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.RequestBuilders;
using WireMock.Settings;
using WireMock.Validation;
using WireMock.Owin;
namespace WireMock.Server
{
/// <summary>
/// The fluent mock server.
/// </summary>
public partial class FluentMockServer : IDisposable
{
private readonly IOwinSelfHost _httpServer;
private readonly object _syncRoot = new object();
private readonly WireMockMiddlewareOptions _options = new WireMockMiddlewareOptions();
/// <summary>
/// Gets the ports.
/// </summary>
/// <value>
/// The ports.
/// </value>
[PublicAPI]
public List<int> Ports { get; }
/// <summary>
/// Gets the urls.
/// </summary>
[PublicAPI]
public string[] Urls { get; }
/// <summary>
/// Gets the mappings.
/// </summary>
[PublicAPI]
public IEnumerable<Mapping> Mappings
{
get
{
lock (((ICollection)_options.Mappings).SyncRoot)
{
return new ReadOnlyCollection<Mapping>(_options.Mappings);
}
}
}
#region Start/Stop
/// <summary>
/// Starts the specified settings.
/// </summary>
/// <param name="settings">The FluentMockServerSettings.</param>
/// <returns>The <see cref="FluentMockServer"/>.</returns>
[PublicAPI]
public static FluentMockServer Start(FluentMockServerSettings settings)
{
Check.NotNull(settings, nameof(settings));
return new FluentMockServer(settings);
}
/// <summary>
/// Start this FluentMockServer.
/// </summary>
/// <param name="port">The port.</param>
/// <param name="ssl">The SSL support.</param>
/// <returns>The <see cref="FluentMockServer"/>.</returns>
[PublicAPI]
public static FluentMockServer Start([CanBeNull] int? port = 0, bool ssl = false)
{
return new FluentMockServer(new FluentMockServerSettings
{
Port = port,
UseSSL = ssl
});
}
/// <summary>
/// Start this FluentMockServer.
/// </summary>
/// <param name="urls">The urls to listen on.</param>
/// <returns>The <see cref="FluentMockServer"/>.</returns>
[PublicAPI]
public static FluentMockServer Start(params string[] urls)
{
Check.NotEmpty(urls, nameof(urls));
return new FluentMockServer(new FluentMockServerSettings
{
Urls = urls
});
}
/// <summary>
/// Start this FluentMockServer with the admin interface.
/// </summary>
/// <param name="port">The port.</param>
/// <param name="ssl">The SSL support.</param>
/// <returns>The <see cref="FluentMockServer"/>.</returns>
[PublicAPI]
public static FluentMockServer StartWithAdminInterface(int? port = 0, bool ssl = false)
{
return new FluentMockServer(new FluentMockServerSettings
{
Port = port,
UseSSL = ssl,
StartAdminInterface = true
});
}
/// <summary>
/// Start this FluentMockServer with the admin interface.
/// </summary>
/// <param name="urls">The urls.</param>
/// <returns>The <see cref="FluentMockServer"/>.</returns>
[PublicAPI]
public static FluentMockServer StartWithAdminInterface(params string[] urls)
{
Check.NotEmpty(urls, nameof(urls));
return new FluentMockServer(new FluentMockServerSettings
{
Urls = urls,
StartAdminInterface = true
});
}
/// <summary>
/// Start this FluentMockServer with the admin interface and read static mappings.
/// </summary>
/// <param name="urls">The urls.</param>
/// <returns>The <see cref="FluentMockServer"/>.</returns>
[PublicAPI]
public static FluentMockServer StartWithAdminInterfaceAndReadStaticMappings(params string[] urls)
{
Check.NotEmpty(urls, nameof(urls));
return new FluentMockServer(new FluentMockServerSettings
{
Urls = urls,
StartAdminInterface = true,
ReadStaticMappings = true
});
}
private FluentMockServer(FluentMockServerSettings settings)
{
if (settings.Urls != null)
{
Urls = settings.Urls;
}
else
{
int port = settings.Port > 0 ? settings.Port.Value : PortUtil.FindFreeTcpPort();
Urls = new[] { (settings.UseSSL == true ? "https" : "http") + "://localhost:" + port + "/" };
}
#if NETSTANDARD
_httpServer = new AspNetCoreSelfHost(_options, Urls);
#else
_httpServer = new OwinSelfHost(_options, Urls);
#endif
Ports = _httpServer.Ports;
_httpServer.StartAsync();
if (settings.AllowPartialMapping == true)
{
AllowPartialMapping();
}
if (settings.StartAdminInterface == true)
{
if (!string.IsNullOrEmpty(settings.AdminUsername) && !string.IsNullOrEmpty(settings.AdminPassword))
{
SetBasicAuthentication(settings.AdminUsername, settings.AdminPassword);
}
InitAdmin();
}
if (settings.ReadStaticMappings == true)
{
ReadStaticMappings();
}
if (settings.ProxyAndRecordSettings != null)
{
InitProxyAndRecord(settings.ProxyAndRecordSettings);
}
}
/// <summary>
/// Stop this server.
/// </summary>
[PublicAPI]
public void Stop()
{
_httpServer?.StopAsync();
}
#endregion
/// <summary>
/// Adds the catch all mapping.
/// </summary>
[PublicAPI]
public void AddCatchAllMapping()
{
Given(Request.Create().WithPath("/*").UsingAnyVerb())
.WithGuid(Guid.Parse("90008000-0000-4444-a17e-669cd84f1f05"))
.AtPriority(1000)
.RespondWith(new DynamicResponseProvider(request => new ResponseMessage { StatusCode = 404, Body = "No matching mapping found" }));
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
if (_httpServer != null && _httpServer.IsStarted)
{
_httpServer.StopAsync();
}
}
/// <summary>
/// Resets LogEntries and Mappings.
/// </summary>
[PublicAPI]
public void Reset()
{
ResetLogEntries();
ResetMappings();
}
/// <summary>
/// Resets the Mappings.
/// </summary>
[PublicAPI]
public void ResetMappings()
{
lock (((ICollection)_options.Mappings).SyncRoot)
{
_options.Mappings = _options.Mappings.Where(m => m.IsAdminInterface).ToList();
}
}
/// <summary>
/// Deletes the mapping.
/// </summary>
/// <param name="guid">The unique identifier.</param>
[PublicAPI]
public bool DeleteMapping(Guid guid)
{
lock (((ICollection)_options.Mappings).SyncRoot)
{
// Check a mapping exists with the same GUID, if so, remove it.
var existingMapping = _options.Mappings.FirstOrDefault(m => m.Guid == guid);
if (existingMapping != null)
{
_options.Mappings.Remove(existingMapping);
return true;
}
return false;
}
}
/// <summary>
/// The add request processing delay.
/// </summary>
/// <param name="delay">The delay.</param>
[PublicAPI]
public void AddGlobalProcessingDelay(TimeSpan delay)
{
lock (_syncRoot)
{
_options.RequestProcessingDelay = delay;
}
}
/// <summary>
/// Allows the partial mapping.
/// </summary>
[PublicAPI]
public void AllowPartialMapping()
{
lock (_syncRoot)
{
_options.AllowPartialMapping = true;
}
}
/// <summary>
/// Sets the basic authentication.
/// </summary>
/// <param name="username">The username.</param>
/// <param name="password">The password.</param>
[PublicAPI]
public void SetBasicAuthentication([NotNull] string username, [NotNull] string password)
{
Check.NotNull(username, nameof(username));
Check.NotNull(password, nameof(password));
string authorization = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password));
_options.AuthorizationMatcher = new RegexMatcher("^(?i)BASIC " + authorization + "$");
}
/// <summary>
/// The given.
/// </summary>
/// <param name="requestMatcher">The request matcher.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
[PublicAPI]
public IRespondWithAProvider Given(IRequestMatcher requestMatcher)
{
return new RespondWithAProvider(RegisterMapping, requestMatcher);
}
/// <summary>
/// The register mapping.
/// </summary>
/// <param name="mapping">
/// The mapping.
/// </param>
private void RegisterMapping(Mapping mapping)
{
lock (((ICollection)_options.Mappings).SyncRoot)
{
// Check a mapping exists with the same GUID, if so, remove it first.
DeleteMapping(mapping.Guid);
_options.Mappings.Add(mapping);
}
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using JetBrains.Annotations;
using WireMock.Http;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.RequestBuilders;
using WireMock.Settings;
using WireMock.Validation;
using WireMock.Owin;
namespace WireMock.Server
{
/// <summary>
/// The fluent mock server.
/// </summary>
public partial class FluentMockServer : IDisposable
{
private readonly IOwinSelfHost _httpServer;
private readonly object _syncRoot = new object();
private readonly WireMockMiddlewareOptions _options = new WireMockMiddlewareOptions();
/// <summary>
/// Gets the ports.
/// </summary>
/// <value>
/// The ports.
/// </value>
[PublicAPI]
public List<int> Ports { get; }
/// <summary>
/// Gets the urls.
/// </summary>
[PublicAPI]
public string[] Urls { get; }
/// <summary>
/// Gets the mappings.
/// </summary>
[PublicAPI]
public IEnumerable<Mapping> Mappings
{
get
{
lock (((ICollection)_options.Mappings).SyncRoot)
{
return new ReadOnlyCollection<Mapping>(_options.Mappings);
}
}
}
#region Start/Stop
/// <summary>
/// Starts the specified settings.
/// </summary>
/// <param name="settings">The FluentMockServerSettings.</param>
/// <returns>The <see cref="FluentMockServer"/>.</returns>
[PublicAPI]
public static FluentMockServer Start(FluentMockServerSettings settings)
{
Check.NotNull(settings, nameof(settings));
return new FluentMockServer(settings);
}
/// <summary>
/// Start this FluentMockServer.
/// </summary>
/// <param name="port">The port.</param>
/// <param name="ssl">The SSL support.</param>
/// <returns>The <see cref="FluentMockServer"/>.</returns>
[PublicAPI]
public static FluentMockServer Start([CanBeNull] int? port = 0, bool ssl = false)
{
return new FluentMockServer(new FluentMockServerSettings
{
Port = port,
UseSSL = ssl
});
}
/// <summary>
/// Start this FluentMockServer.
/// </summary>
/// <param name="urls">The urls to listen on.</param>
/// <returns>The <see cref="FluentMockServer"/>.</returns>
[PublicAPI]
public static FluentMockServer Start(params string[] urls)
{
Check.NotEmpty(urls, nameof(urls));
return new FluentMockServer(new FluentMockServerSettings
{
Urls = urls
});
}
/// <summary>
/// Start this FluentMockServer with the admin interface.
/// </summary>
/// <param name="port">The port.</param>
/// <param name="ssl">The SSL support.</param>
/// <returns>The <see cref="FluentMockServer"/>.</returns>
[PublicAPI]
public static FluentMockServer StartWithAdminInterface(int? port = 0, bool ssl = false)
{
return new FluentMockServer(new FluentMockServerSettings
{
Port = port,
UseSSL = ssl,
StartAdminInterface = true
});
}
/// <summary>
/// Start this FluentMockServer with the admin interface.
/// </summary>
/// <param name="urls">The urls.</param>
/// <returns>The <see cref="FluentMockServer"/>.</returns>
[PublicAPI]
public static FluentMockServer StartWithAdminInterface(params string[] urls)
{
Check.NotEmpty(urls, nameof(urls));
return new FluentMockServer(new FluentMockServerSettings
{
Urls = urls,
StartAdminInterface = true
});
}
/// <summary>
/// Start this FluentMockServer with the admin interface and read static mappings.
/// </summary>
/// <param name="urls">The urls.</param>
/// <returns>The <see cref="FluentMockServer"/>.</returns>
[PublicAPI]
public static FluentMockServer StartWithAdminInterfaceAndReadStaticMappings(params string[] urls)
{
Check.NotEmpty(urls, nameof(urls));
return new FluentMockServer(new FluentMockServerSettings
{
Urls = urls,
StartAdminInterface = true,
ReadStaticMappings = true
});
}
private FluentMockServer(FluentMockServerSettings settings)
{
if (settings.Urls != null)
{
Urls = settings.Urls;
}
else
{
int port = settings.Port > 0 ? settings.Port.Value : PortUtil.FindFreeTcpPort();
Urls = new[] { (settings.UseSSL == true ? "https" : "http") + "://localhost:" + port + "/" };
}
#if NETSTANDARD
_httpServer = new AspNetCoreSelfHost(_options, Urls);
#else
_httpServer = new OwinSelfHost(_options, Urls);
#endif
Ports = _httpServer.Ports;
_httpServer.StartAsync();
if (settings.AllowPartialMapping == true)
{
AllowPartialMapping();
}
if (settings.StartAdminInterface == true)
{
if (!string.IsNullOrEmpty(settings.AdminUsername) && !string.IsNullOrEmpty(settings.AdminPassword))
{
SetBasicAuthentication(settings.AdminUsername, settings.AdminPassword);
}
InitAdmin();
}
if (settings.ReadStaticMappings == true)
{
ReadStaticMappings();
}
if (settings.ProxyAndRecordSettings != null)
{
InitProxyAndRecord(settings.ProxyAndRecordSettings);
}
if (settings.MaxRequestLogCount != null)
{
SetMaxRequestLogCount(settings.MaxRequestLogCount);
}
}
/// <summary>
/// Stop this server.
/// </summary>
[PublicAPI]
public void Stop()
{
_httpServer?.StopAsync();
}
#endregion
/// <summary>
/// Adds the catch all mapping.
/// </summary>
[PublicAPI]
public void AddCatchAllMapping()
{
Given(Request.Create().WithPath("/*").UsingAnyVerb())
.WithGuid(Guid.Parse("90008000-0000-4444-a17e-669cd84f1f05"))
.AtPriority(1000)
.RespondWith(new DynamicResponseProvider(request => new ResponseMessage { StatusCode = 404, Body = "No matching mapping found" }));
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
if (_httpServer != null && _httpServer.IsStarted)
{
_httpServer.StopAsync();
}
}
/// <summary>
/// Resets LogEntries and Mappings.
/// </summary>
[PublicAPI]
public void Reset()
{
ResetLogEntries();
ResetMappings();
}
/// <summary>
/// Resets the Mappings.
/// </summary>
[PublicAPI]
public void ResetMappings()
{
lock (((ICollection)_options.Mappings).SyncRoot)
{
_options.Mappings = _options.Mappings.Where(m => m.IsAdminInterface).ToList();
}
}
/// <summary>
/// Deletes the mapping.
/// </summary>
/// <param name="guid">The unique identifier.</param>
[PublicAPI]
public bool DeleteMapping(Guid guid)
{
lock (((ICollection)_options.Mappings).SyncRoot)
{
// Check a mapping exists with the same GUID, if so, remove it.
var existingMapping = _options.Mappings.FirstOrDefault(m => m.Guid == guid);
if (existingMapping != null)
{
_options.Mappings.Remove(existingMapping);
return true;
}
return false;
}
}
/// <summary>
/// The add request processing delay.
/// </summary>
/// <param name="delay">The delay.</param>
[PublicAPI]
public void AddGlobalProcessingDelay(TimeSpan delay)
{
lock (_syncRoot)
{
_options.RequestProcessingDelay = delay;
}
}
/// <summary>
/// Allows the partial mapping.
/// </summary>
[PublicAPI]
public void AllowPartialMapping()
{
lock (_syncRoot)
{
_options.AllowPartialMapping = true;
}
}
/// <summary>
/// Sets the basic authentication.
/// </summary>
/// <param name="username">The username.</param>
/// <param name="password">The password.</param>
[PublicAPI]
public void SetBasicAuthentication([NotNull] string username, [NotNull] string password)
{
Check.NotNull(username, nameof(username));
Check.NotNull(password, nameof(password));
string authorization = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password));
_options.AuthorizationMatcher = new RegexMatcher("^(?i)BASIC " + authorization + "$");
}
/// <summary>
/// Removes the basic authentication.
/// </summary>
[PublicAPI]
public void RemoveBasicAuthentication()
{
_options.AuthorizationMatcher = null;
}
/// <summary>
/// Sets the maximum RequestLog count.
/// </summary>
/// <param name="maxRequestLogCount">The maximum RequestLog count.</param>
[PublicAPI]
public void SetMaxRequestLogCount([CanBeNull] int? maxRequestLogCount)
{
_options.MaxRequestLogCount = maxRequestLogCount;
}
/// <summary>
/// Sets RequestLog expiration in hours.
/// </summary>
/// <param name="requestLogExpirationDuration">The RequestLog expiration in hours.</param>
[PublicAPI]
public void SetRequestLogExpirationDuration([CanBeNull] int? requestLogExpirationDuration)
{
_options.RequestLogExpirationDuration = requestLogExpirationDuration;
}
/// <summary>
/// The given.
/// </summary>
/// <param name="requestMatcher">The request matcher.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
[PublicAPI]
public IRespondWithAProvider Given(IRequestMatcher requestMatcher)
{
return new RespondWithAProvider(RegisterMapping, requestMatcher);
}
/// <summary>
/// The register mapping.
/// </summary>
/// <param name="mapping">
/// The mapping.
/// </param>
private void RegisterMapping(Mapping mapping)
{
lock (((ICollection)_options.Mappings).SyncRoot)
{
// Check a mapping exists with the same GUID, if so, remove it first.
DeleteMapping(mapping.Guid);
_options.Mappings.Add(mapping);
}
}
}
}

View File

@@ -69,5 +69,15 @@
/// The password needed for __admin access.
/// </summary>
public string AdminPassword { get; set; }
/// <summary>
/// The RequestLog expiration in hours (optional).
/// </summary>
public int? RequestLogExpirationDuration { get; set; }
/// <summary>
/// The MaxRequestLog count (optional).
/// </summary>
public int? MaxRequestLogCount { get; set; }
}
}

View File

@@ -18,7 +18,7 @@
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/WireMock-Net/WireMock.Net</RepositoryUrl>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<DebugType>full</DebugType>
<DebugType>portable</DebugType>
<ApplicationIcon>../../WireMock.Net-Logo.ico</ApplicationIcon>
<RootNamespace>WireMock</RootNamespace>
</PropertyGroup>

View File

@@ -33,10 +33,10 @@ namespace WireMock.Net.Tests
[Fact]
public void FluentMockServer_StartStop()
{
var server1 = FluentMockServer.Start("http://localhost:9090/");
var server1 = FluentMockServer.Start("http://localhost:9091/");
server1.Stop();
var server2 = FluentMockServer.Start("http://localhost:9090/");
var server2 = FluentMockServer.Start("http://localhost:9091/");
server2.Stop();
}
@@ -370,7 +370,7 @@ namespace WireMock.Net.Tests
}
//Leaving commented as this requires an actual certificate with password, along with a service that expects a client certificate
[Fact]
//[Fact]
//public async Task Should_proxy_responses_with_client_certificate()
//{
// // given
@@ -386,7 +386,29 @@ namespace WireMock.Net.Tests
// Check.That(result).Contains("google");
//}
//[TearDown]
[Fact]
public async Task FluentMockServer_Logging_SetMaxRequestLogCount()
{
// Assign
var client = new HttpClient();
// Act
_server = FluentMockServer.Start();
_server.SetMaxRequestLogCount(2);
await client.GetAsync("http://localhost:" + _server.Ports[0] + "/foo1");
await client.GetAsync("http://localhost:" + _server.Ports[0] + "/foo2");
await client.GetAsync("http://localhost:" + _server.Ports[0] + "/foo3");
// Assert
Check.That(_server.LogEntries).HasSize(2);
var requestLoggedA = _server.LogEntries.First();
Check.That(requestLoggedA.RequestMessage.Path).EndsWith("/foo2");
var requestLoggedB = _server.LogEntries.Last();
Check.That(requestLoggedB.RequestMessage.Path).EndsWith("/foo3");
}
public void Dispose()
{
_server?.Stop();

View File

@@ -18,7 +18,7 @@
},
"Response": {
"StatusCode": 200,
"Body": "{\"_self\":\"\",\"id\":\"abc\",\"_rid\":\"abc.documents.azure.com\",\"media\":\"//media/\",\"addresses\":\"//addresses/\",\"_dbs\":\"//dbs/\",\"writableLocations\":[{\"name\":\"West Europe\",\"databaseAccountEndpoint\":\"http://localhost:9090/\"}],\"readableLocations\":[{\"name\":\"West Europe\",\"databaseAccountEndpoint\":\"http://localhost:9090/\"}],\"userReplicationPolicy\":{\"asyncReplication\":false,\"minReplicaSetSize\":3,\"maxReplicasetSize\":4},\"userConsistencyPolicy\":{\"defaultConsistencyLevel\":\"Session\"},\"systemReplicationPolicy\":{\"minReplicaSetSize\":3,\"maxReplicasetSize\":4},\"readPolicy\":{\"primaryReadCoefficient\":1,\"secondaryReadCoefficient\":1},\"queryEngineConfiguration\":\"{\\\"maxSqlQueryInputLength\\\":30720,\\\"maxJoinsPerSqlQuery\\\":5,\\\"maxLogicalAndPerSqlQuery\\\":500,\\\"maxLogicalOrPerSqlQuery\\\":500,\\\"maxUdfRefPerSqlQuery\\\":2,\\\"maxInExpressionItemsCount\\\":8000,\\\"queryMaxInMemorySortDocumentCount\\\":500,\\\"maxQueryRequestTimeoutFraction\\\":0.9,\\\"sqlAllowNonFiniteNumbers\\\":false,\\\"sqlAllowAggregateFunctions\\\":true,\\\"sqlAllowSubQuery\\\":false,\\\"allowNewKeywords\\\":true,\\\"sqlAllowLike\\\":false,\\\"maxSpatialQueryCells\\\":12,\\\"spatialMaxGeometryPointCount\\\":256,\\\"sqlAllowTop\\\":true,\\\"enableSpatialIndexing\\\":true}\"}",
"Body": "{\"_self\":\"\",\"id\":\"abc\",\"_rid\":\"abc.documents.azure.com\",\"media\":\"//media/\",\"addresses\":\"//addresses/\",\"_dbs\":\"//dbs/\",\"writableLocations\":[{\"name\":\"West Europe\",\"databaseAccountEndpoint\":\"http://localhost:9091/\"}],\"readableLocations\":[{\"name\":\"West Europe\",\"databaseAccountEndpoint\":\"http://localhost:9091/\"}],\"userReplicationPolicy\":{\"asyncReplication\":false,\"minReplicaSetSize\":3,\"maxReplicasetSize\":4},\"userConsistencyPolicy\":{\"defaultConsistencyLevel\":\"Session\"},\"systemReplicationPolicy\":{\"minReplicaSetSize\":3,\"maxReplicasetSize\":4},\"readPolicy\":{\"primaryReadCoefficient\":1,\"secondaryReadCoefficient\":1},\"queryEngineConfiguration\":\"{\\\"maxSqlQueryInputLength\\\":30720,\\\"maxJoinsPerSqlQuery\\\":5,\\\"maxLogicalAndPerSqlQuery\\\":500,\\\"maxLogicalOrPerSqlQuery\\\":500,\\\"maxUdfRefPerSqlQuery\\\":2,\\\"maxInExpressionItemsCount\\\":8000,\\\"queryMaxInMemorySortDocumentCount\\\":500,\\\"maxQueryRequestTimeoutFraction\\\":0.9,\\\"sqlAllowNonFiniteNumbers\\\":false,\\\"sqlAllowAggregateFunctions\\\":true,\\\"sqlAllowSubQuery\\\":false,\\\"allowNewKeywords\\\":true,\\\"sqlAllowLike\\\":false,\\\"maxSpatialQueryCells\\\":12,\\\"spatialMaxGeometryPointCount\\\":256,\\\"sqlAllowTop\\\":true,\\\"enableSpatialIndexing\\\":true}\"}",
"BodyEncoding": {
"CodePage": 65001,
"EncodingName": "Unicode (UTF-8)",
@@ -33,7 +33,7 @@
"x-ms-gatewayversion": "version=1.11.164.3",
"x-ms-media-storage-usage-mb": "0",
"x-ms-databaseaccount-provisioned-mb": "0",
"Content-Location": "http://localhost:9090/",
"Content-Location": "http://localhost:9091/",
"Date": "Mon, 06 Mar 2017 10:56:40 GMT",
"Content-Type": "application/json",
"Server": "Microsoft-HTTPAPI/2.0"