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) static void Main(string[] args)
{ {
// Create an implementation of the IFluentMockServerAdmin and pass in the base URL for the API. // 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 // Set BASIC Auth
var value = Convert.ToBase64String(Encoding.ASCII.GetBytes("a:b")); var value = Convert.ToBase64String(Encoding.ASCII.GetBytes("a:b"));

View File

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

View File

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

View File

@@ -1,15 +1,42 @@
using System; using System;
using System.Threading;
using WireMock.Server;
namespace WireMock.Net.StandAlone.NETCoreApp namespace WireMock.Net.StandAlone.NETCoreApp
{ {
class Program class Program
{ {
private static int sleepTime = 30000;
private static FluentMockServer server;
static void Main(string[] args) static void Main(string[] args)
{ {
StandAloneApp.Start(args); server = StandAloneApp.Start(args);
Console.WriteLine("Press any key to stop the server"); Console.WriteLine($"{DateTime.UtcNow} Press Ctrl+C to shut down");
Console.ReadKey();
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": { "profiles": {
"WireMock.Net.StandAlone.NETCoreApp": { "WireMock.Net.StandAlone.NETCoreApp": {
"commandName": "Project", "commandName": "Project",
"commandLineArgs": "--Urls http://*:9090" "commandLineArgs": "--Urls http://*:9091"
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -33,10 +33,10 @@ namespace WireMock.Net.Tests
[Fact] [Fact]
public void FluentMockServer_StartStop() public void FluentMockServer_StartStop()
{ {
var server1 = FluentMockServer.Start("http://localhost:9090/"); var server1 = FluentMockServer.Start("http://localhost:9091/");
server1.Stop(); server1.Stop();
var server2 = FluentMockServer.Start("http://localhost:9090/"); var server2 = FluentMockServer.Start("http://localhost:9091/");
server2.Stop(); 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 //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() //public async Task Should_proxy_responses_with_client_certificate()
//{ //{
// // given // // given
@@ -386,7 +386,29 @@ namespace WireMock.Net.Tests
// Check.That(result).Contains("google"); // 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() public void Dispose()
{ {
_server?.Stop(); _server?.Stop();

View File

@@ -18,7 +18,7 @@
}, },
"Response": { "Response": {
"StatusCode": 200, "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": { "BodyEncoding": {
"CodePage": 65001, "CodePage": 65001,
"EncodingName": "Unicode (UTF-8)", "EncodingName": "Unicode (UTF-8)",
@@ -33,7 +33,7 @@
"x-ms-gatewayversion": "version=1.11.164.3", "x-ms-gatewayversion": "version=1.11.164.3",
"x-ms-media-storage-usage-mb": "0", "x-ms-media-storage-usage-mb": "0",
"x-ms-databaseaccount-provisioned-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", "Date": "Mon, 06 Mar 2017 10:56:40 GMT",
"Content-Type": "application/json", "Content-Type": "application/json",
"Server": "Microsoft-HTTPAPI/2.0" "Server": "Microsoft-HTTPAPI/2.0"