Version 2.x (#1359)

* Version 2.x

* Setup .NET 9

* 12

* cleanup some #if for NETSTANDARD1_3

* cleanup + fix tests for net8

* openapi

* NO ConfigureAwait(false) + cleanup

* .

* #endif

* HashSet

* WireMock.Net.NUnit

* HttpContext

* Add WebSockets (#1423)

* Add WebSockets

* Add tests

* fix

* more tests

* Add tests

* ...

* remove IOwin

* -

* tests

* fluent

* ok

* match

* .

* byte[]

* x

* func

* func

* byte

* trans

* ...

* frameworks.........

* jmes

* xxx

* sc

* using var httpClient = new HttpClient();

* usings

* maxRetries

* up

* xunit v3

* ct

* ---

* ct

* ct2

* T Unit

* WireMock.Net.TUnitTests / 10

* t unit first

* --project

* no tunit

* t2

* --project

* --project

* ci -  --project

* publish ./test/wiremock-coverage.xml

* windows

* .

* log

* ...

* log

* goed

* BodyType

* .

* .

* --scenario

* ...

* pact

* ct

* .

* WireMock.Net.RestClient.AwesomeAssertions (#1427)

* WireMock.Net.RestClient.AwesomeAssertions

* ok

* atpath

* fix test

* sonar fixes

* ports

* proxy test

* FIX?

* ---

* await Task.Delay(100, _ct);

* ?

* --project

* Aspire: use IDistributedApplicationEventingSubscriber (#1428)

* broadcast

* ok

* more tsts

* .

* Collection

* up

* .

* 2

* remove nfluent

* <VersionPrefix>2.0.0-preview-02</VersionPrefix>

* ...

* .

* nuget icon

* .

* <PackageReference Include="JmesPath.Net" Version="1.1.0" />

* x

* 500

* .

* fix some warnings

* ws
This commit is contained in:
Stef Heyenrath
2026-03-11 17:02:47 +01:00
committed by GitHub
parent d6e19532bc
commit a292f28dda
521 changed files with 79740 additions and 5246 deletions

View File

@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
#if !NETSTANDARD1_3
using System;
using System.Diagnostics.CodeAnalysis;
using System.IdentityModel.Tokens.Jwt;
using System.Text.RegularExpressions;
@@ -111,5 +109,4 @@ internal class AzureADAuthenticationMatcher : IStringMatcher
tenant = null;
return false;
}
}
#endif
}

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Text;
using WireMock.Matchers;

View File

@@ -1,18 +0,0 @@
// Copyright © WireMock.Net
#if NET451 || NET452 || NET46 || NET451 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0
using System.Text.RegularExpressions;
using WireMock.Constants;
// ReSharper disable once CheckNamespace
namespace System;
internal static class StringExtensions
{
public static string Replace(this string text, string oldValue, string newValue, StringComparison stringComparison)
{
var options = stringComparison == StringComparison.OrdinalIgnoreCase ? RegexOptions.IgnoreCase : RegexOptions.None;
return Regex.Replace(text, oldValue, newValue, options, RegexConstants.DefaultTimeout);
}
}
#endif

View File

@@ -1,28 +0,0 @@
// Copyright © WireMock.Net
#if NETSTANDARD1_3
// ReSharper disable once CheckNamespace
namespace System.Net;
internal class WebProxy : IWebProxy
{
private readonly string _proxy;
public ICredentials? Credentials { get; set; }
public WebProxy(string proxy)
{
_proxy = proxy;
}
public Uri GetProxy(Uri destination)
{
return new Uri(_proxy);
}
public bool IsBypassed(Uri host)
{
return true;
}
}
#endif

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using WireMock.Models;
namespace WireMock.Extensions;

View File

@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System.Collections.Generic;
using System.IO;
using WireMock.Util;
using Stef.Validation;

View File

@@ -2,6 +2,7 @@
using System.Net;
using System.Net.Http;
using System.Security.Authentication;
using WireMock.HttpsCertificate;
using WireMock.Settings;
@@ -11,29 +12,21 @@ internal static class HttpClientBuilder
{
public static HttpClient Build(HttpClientSettings settings)
{
#if NETSTANDARD || NETCOREAPP3_1 || NET5_0_OR_GREATER
#if NET8_0_OR_GREATER
var handler = new HttpClientHandler
{
CheckCertificateRevocationList = false,
#if NET5_0_OR_GREATER
SslProtocols = System.Security.Authentication.SslProtocols.Tls13 | System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls11 | System.Security.Authentication.SslProtocols.Tls,
#else
SslProtocols = System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls11 | System.Security.Authentication.SslProtocols.Tls,
#endif
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true,
#pragma warning disable SYSLIB0039 // Type or member is obsolete
SslProtocols = SslProtocols.Tls13 | SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls,
#pragma warning restore SYSLIB0039 // Type or member is obsolete
ServerCertificateCustomValidationCallback = (_, _, _, _) => true,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};
#elif NET46
#else
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};
#else
var handler = new WebRequestHandler
{
ServerCertificateValidationCallback = (sender, certificate, chain, errors) => true,
ServerCertificateCustomValidationCallback = (_, _, _, _) => true,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};
#endif
@@ -67,13 +60,12 @@ internal static class HttpClientBuilder
}
}
#if NET5_0_OR_GREATER
#if NET8_0_OR_GREATER
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
ServicePointManager.ServerCertificateValidationCallback = (message, cert, chain, errors) => true;
#elif !NETSTANDARD1_3
#else
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
ServicePointManager.ServerCertificateValidationCallback = (message, cert, chain, errors) => true;
#endif
ServicePointManager.ServerCertificateValidationCallback = (message, cert, chain, errors) => true;
return HttpClientFactory2.Create(handler);
}

View File

@@ -28,7 +28,7 @@ internal static class HttpClientFactory2
var next = handler;
foreach (var delegatingHandler in delegatingHandlers.Reverse())
foreach (var delegatingHandler in Enumerable.Reverse(delegatingHandlers))
{
delegatingHandler.InnerHandler = next;
next = delegatingHandler;

View File

@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
@@ -9,7 +7,6 @@ using Newtonsoft.Json;
using Stef.Validation;
using WireMock.Constants;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.Http;

View File

@@ -1,11 +1,8 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using WireMock.Util;
namespace WireMock.Http;

View File

@@ -1,12 +1,8 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Stef.Validation;
using WireMock.Models;
using WireMock.Settings;

View File

@@ -1,5 +1,6 @@
// Copyright © WireMock.Net
#if NET5_0_OR_GREATER
#if NET8_0_OR_GREATER
using System;
using System.Net.Http;
using WireMock.Server;

View File

@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using WireMock.Owin;
@@ -43,20 +41,17 @@ internal static class CertificateLoader
}
finally
{
#if NETSTANDARD || NET46
certStore.Dispose();
#else
certStore.Close();
#endif
}
}
if (!string.IsNullOrEmpty(options.X509CertificateFilePath))
{
if (options.X509CertificateFilePath.EndsWith(ExtensionPem, StringComparison.OrdinalIgnoreCase))
if (options.X509CertificateFilePath!.EndsWith(ExtensionPem, StringComparison.OrdinalIgnoreCase))
{
// PEM logic based on: https://www.scottbrady91.com/c-sharp/pem-loading-in-dotnet-core-and-dotnet
#if NET5_0_OR_GREATER
#if NET8_0_OR_GREATER
if (!string.IsNullOrEmpty(options.X509CertificatePassword))
{
var certPem = File.ReadAllText(options.X509CertificateFilePath);
@@ -66,18 +61,8 @@ internal static class CertificateLoader
return new X509Certificate2(cert.Export(X509ContentType.Pfx, defaultPasswordPem), defaultPasswordPem);
}
return X509Certificate2.CreateFromPemFile(options.X509CertificateFilePath);
#elif NETCOREAPP3_1
var cert = new X509Certificate2(options.X509CertificateFilePath);
if (!string.IsNullOrEmpty(options.X509CertificatePassword))
{
var key = System.Security.Cryptography.ECDsa.Create()!;
key.ImportECPrivateKey(System.Text.Encoding.UTF8.GetBytes(options.X509CertificatePassword), out _);
return cert.CopyWithPrivateKey(key);
}
return cert;
#else
throw new InvalidOperationException("Loading a PEM Certificate is only supported for .NET Core App 3.1, .NET 5.0 and higher.");
throw new InvalidOperationException("Loading a PEM Certificate is only supported for .NET 8.0 and higher.");
#endif
}
@@ -123,11 +108,8 @@ internal static class CertificateLoader
}
finally
{
#if NETSTANDARD || NET46
certStore.Dispose();
#else
certStore.Close();
#endif
}
}
}

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Security.Cryptography.X509Certificates;
namespace WireMock.HttpsCertificate;

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using WireMock.Admin.Mappings;
using WireMock.Matchers.Request;
using WireMock.Server;

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using WireMock.Matchers.Request;
namespace WireMock.Logging;
@@ -14,13 +13,13 @@ public class LogEntry : ILogEntry
public Guid Guid { get; set; }
/// <inheritdoc cref="ILogEntry.RequestMessage" />
public IRequestMessage RequestMessage { get; set; } = null!;
public IRequestMessage? RequestMessage { get; set; }
/// <inheritdoc cref="ILogEntry.ResponseMessage" />
public IResponseMessage ResponseMessage { get; set; } = null!;
public IResponseMessage? ResponseMessage { get; set; }
/// <inheritdoc cref="ILogEntry.RequestMatchResult" />
public IRequestMatchResult RequestMatchResult { get; set; } = null!;
public IRequestMatchResult? RequestMatchResult { get; set; }
/// <inheritdoc cref="ILogEntry.MappingGuid" />
public Guid? MappingGuid { get; set; }
@@ -35,5 +34,5 @@ public class LogEntry : ILogEntry
public string? PartialMappingTitle { get; set; }
/// <inheritdoc cref="ILogEntry.PartialMatchResult" />
public IRequestMatchResult PartialMatchResult { get; set; } = null!;
public IRequestMatchResult? PartialMatchResult { get; set; }
}

View File

@@ -1,7 +1,6 @@
// Copyright © WireMock.Net
using Newtonsoft.Json;
using System;
using WireMock.Admin.Requests;
namespace WireMock.Logging;

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using WireMock.Admin.Requests;
namespace WireMock.Logging;

View File

@@ -1,7 +1,6 @@
// Copyright © WireMock.Net
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Stef.Validation;
using WireMock.Matchers.Request;
using WireMock.Models;
@@ -145,9 +144,9 @@ public class Mapping : IMapping
}
/// <inheritdoc />
public Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IRequestMessage requestMessage)
public Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(HttpContext context, IRequestMessage requestMessage)
{
return Provider.ProvideResponseAsync(this, requestMessage, Settings);
return Provider.ProvideResponseAsync(this, context, requestMessage, Settings);
}
/// <inheritdoc />

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Linq;
using System.Text;
using Stef.Validation;

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using WireMock.Util;

View File

@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System;
namespace WireMock.Matchers;
/// <summary>

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Linq;
using AnyOfTypes;
using Stef.Validation;

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System.Collections.Generic;
using System.Linq;
using AnyOfTypes;
using Stef.Validation;

View File

@@ -0,0 +1,85 @@
// Copyright © WireMock.Net
using Stef.Validation;
using WireMock.Extensions;
namespace WireMock.Matchers;
/// <summary>
/// FuncMatcher - matches using a custom function
/// </summary>
/// <inheritdoc cref="IFuncMatcher"/>
public class FuncMatcher : IFuncMatcher
{
private readonly Func<string?, bool>? _stringFunc;
private readonly Func<byte[]?, bool>? _bytesFunc;
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <summary>
/// Initializes a new instance of the <see cref="FuncMatcher"/> class for string matching.
/// </summary>
/// <param name="func">The function to check if a string is a match.</param>
/// <param name="matchBehaviour">The match behaviour.</param>
public FuncMatcher(Func<string?, bool> func, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
_stringFunc = Guard.NotNull(func);
MatchBehaviour = matchBehaviour;
}
/// <summary>
/// Initializes a new instance of the <see cref="FuncMatcher"/> class for byte array matching.
/// </summary>
/// <param name="func">The function to check if a byte[] is a match.</param>
/// <param name="matchBehaviour">The match behaviour.</param>
public FuncMatcher(Func<byte[]?, bool> func, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
_bytesFunc = Guard.NotNull(func);
MatchBehaviour = matchBehaviour;
}
/// <inheritdoc />
public MatchResult IsMatch(object? value)
{
if (value is string stringValue && _stringFunc != null)
{
try
{
return MatchResult.From(Name, MatchBehaviour, _stringFunc(stringValue));
}
catch (Exception ex)
{
return MatchResult.From(Name, ex);
}
}
if (value is byte[] bytesValue && _bytesFunc != null)
{
try
{
return MatchResult.From(Name, MatchBehaviour, _bytesFunc(bytesValue));
}
catch (Exception ex)
{
return MatchResult.From(Name, ex);
}
}
return MatchResult.From(Name, MatchScores.Mismatch);
}
/// <inheritdoc />
public string Name => nameof(FuncMatcher);
/// <inheritdoc />
public string GetCSharpCodeArguments()
{
var funcType = _stringFunc != null ? "Func<string?, bool>" : "Func<byte[]?, bool>";
return $"new {Name}" +
$"(" +
$"/* {funcType} function */, " +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}" +
$")";
}
}

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Linq;
using AnyOfTypes;
using Newtonsoft.Json.Linq;
@@ -71,7 +70,7 @@ public class JsonPathMatcher : IStringMatcher, IObjectMatcher
{
try
{
var jToken = JToken.Parse(input);
var jToken = JToken.Parse(input!);
score = IsMatch(jToken);
}
catch (Exception ex)

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Linq;
using AnyOfTypes;
using DevLab.JmesPath;

View File

@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using Stef.Validation;

View File

@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Linq;
namespace WireMock.Matchers.Request;

View File

@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Linq;
using Stef.Validation;
using WireMock.Matchers.Helpers;

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using Newtonsoft.Json.Linq;
using Stef.Validation;

View File

@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Linq;
using AnyOfTypes;
using Stef.Validation;

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System.Collections.Generic;
using System.Linq;
using Stef.Validation;

View File

@@ -1,8 +1,6 @@
// Copyright © WireMock.Net
using Stef.Validation;
using System;
using System.Collections.Generic;
using System.Linq;
namespace WireMock.Matchers.Request;

View File

@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Linq;
using Stef.Validation;
using WireMock.Types;

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Linq;
using Stef.Validation;

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Linq;
using Stef.Validation;

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Linq;
using Stef.Validation;
using WireMock.Util;

View File

@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Linq;
using Stef.Validation;
using WireMock.Types;

View File

@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Linq;
using AnyOfTypes;
using Stef.Validation;

View File

@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Linq;
using AnyOfTypes;
using Stef.Validation;

View File

@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.XPath;
@@ -11,9 +9,7 @@ using WireMock.Models;
using Stef.Validation;
using WireMock.Admin.Mappings;
using WireMock.Util;
#if !NETSTANDARD1_3
using Wmhelp.XPath2;
#endif
namespace WireMock.Matchers;
@@ -135,7 +131,7 @@ public class XPathMatcher : IStringMatcher
}
catch
{
_xmlDocument = default;
_xmlDocument = null;
}
}
@@ -151,25 +147,17 @@ public class XPathMatcher : IStringMatcher
var xmlNamespaceManager = GetXmlNamespaceManager(xmlNamespaceMap);
if (xmlNamespaceManager == null)
{
#if NETSTANDARD1_3
return navigator.Evaluate(xpath);
#else
return navigator.XPath2Evaluate(xpath);
#endif
}
#if NETSTANDARD1_3
return navigator.Evaluate(xpath, xmlNamespaceManager);
#else
return navigator.XPath2Evaluate(xpath, xmlNamespaceManager);
#endif
}
private XmlNamespaceManager? GetXmlNamespaceManager(IEnumerable<XmlNamespace>? xmlNamespaceMap)
{
if (_xpathNavigator == null || xmlNamespaceMap == null)
{
return default;
return null;
}
var nsManager = new XmlNamespaceManager(_xpathNavigator.NameTable);

View File

@@ -1,9 +1,6 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
namespace WireMock.Models;

View File

@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System;
namespace WireMock.Models;
/// <summary>

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using Stef.Validation;
namespace WireMock.Models;

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System.Collections.Generic;
using WireMock.Types;
using WireMock.Util;

View File

@@ -1,38 +0,0 @@
// Copyright © WireMock.Net
#if ACTIVITY_TRACING_SUPPORTED
namespace WireMock.Owin.ActivityTracing;
/// <summary>
/// Options for controlling activity tracing in WireMock.Net middleware.
/// These options control the creation of System.Diagnostics.Activity objects
/// but do not require any OpenTelemetry exporter dependencies.
/// </summary>
public class ActivityTracingOptions
{
/// <summary>
/// Gets or sets a value indicating whether to exclude admin interface requests from tracing.
/// Default is <c>true</c>.
/// </summary>
public bool ExcludeAdminRequests { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether to record request body in trace attributes.
/// Default is <c>false</c> due to potential PII concerns.
/// </summary>
public bool RecordRequestBody { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to record response body in trace attributes.
/// Default is <c>false</c> due to potential PII concerns.
/// </summary>
public bool RecordResponseBody { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to record mapping match details in trace attributes.
/// Default is <c>true</c>.
/// </summary>
public bool RecordMatchDetails { get; set; } = true;
}
#endif

View File

@@ -1,34 +0,0 @@
// Copyright © WireMock.Net
#if !ACTIVITY_TRACING_SUPPORTED
using System;
#endif
using WireMock.Settings;
namespace WireMock.Owin.ActivityTracing;
/// <summary>
/// Validator for Activity Tracing configuration.
/// </summary>
internal static class ActivityTracingValidator
{
/// <summary>
/// Validates that Activity Tracing is supported on the current framework.
/// Throws an exception if ActivityTracingOptions is configured on an unsupported framework.
/// </summary>
/// <param name="settings">The WireMock server settings to validate.</param>
/// <exception cref="System.InvalidOperationException">
/// Thrown when ActivityTracingOptions is configured but the current framework does not support System.Diagnostics.Activity.
/// </exception>
public static void ValidateActivityApiPresence(WireMockServerSettings settings)
{
#if !ACTIVITY_TRACING_SUPPORTED
if (settings.ActivityTracingOptions is not null)
{
throw new InvalidOperationException(
"Activity Tracing is not supported on this target framework. " +
"It requires .NET 5.0 or higher which includes System.Diagnostics.Activity support.");
}
#endif
}
}

View File

@@ -1,16 +1,17 @@
// Copyright © WireMock.Net
#if ACTIVITY_TRACING_SUPPORTED
using System;
using System.Diagnostics;
using System.Net.WebSockets;
using WireMock.Logging;
using WireMock.Settings;
using WireMock.WebSockets;
namespace WireMock.Owin.ActivityTracing;
/// <summary>
/// Provides an ActivitySource for WireMock.Net distributed tracing.
/// </summary>
public static class WireMockActivitySource
internal static class WireMockActivitySource
{
/// <summary>
/// The name of the ActivitySource used by WireMock.Net.
@@ -103,11 +104,11 @@ public static class WireMockActivitySource
// Set status based on HTTP status code (using standard otel.status_code tag)
if (statusCodeInt.Value >= 400)
{
activity.SetTag("otel.status_code", "ERROR");
activity.SetTag(WireMockSemanticConventions.OtelStatusCode, "ERROR");
}
else
{
activity.SetTag("otel.status_code", "OK");
activity.SetTag(WireMockSemanticConventions.OtelStatusCode, "OK");
}
}
@@ -190,11 +191,65 @@ public static class WireMockActivitySource
}
// Use standard OpenTelemetry exception semantic conventions
activity.SetTag("otel.status_code", "ERROR");
activity.SetTag(WireMockSemanticConventions.OtelStatusCode, "ERROR");
activity.SetTag("otel.status_description", exception.Message);
activity.SetTag("exception.type", exception.GetType().FullName);
activity.SetTag("exception.message", exception.Message);
activity.SetTag("exception.stacktrace", exception.ToString());
}
}
#endif
/// <summary>
/// Starts a new activity for a WebSocket message.
/// </summary>
/// <param name="direction">The direction of the message.</param>
/// <param name="mappingGuid">The GUID of the mapping handling the WebSocket.</param>
/// <returns>The started activity, or null if tracing is not enabled.</returns>
internal static Activity? StartWebSocketMessageActivity(WebSocketMessageDirection direction, Guid mappingGuid)
{
if (!Source.HasListeners())
{
return null;
}
var activity = Source.StartActivity(
$"WireMock WebSocket {direction.ToString().ToLowerInvariant()}",
ActivityKind.Server
);
if (activity != null)
{
activity.SetTag(WireMockSemanticConventions.MappingGuid, mappingGuid.ToString());
}
return activity;
}
/// <summary>
/// Enriches an activity with WebSocket message information.
/// </summary>
internal static void EnrichWithWebSocketMessage(
Activity? activity,
WebSocketMessageType messageType,
int messageSize,
bool endOfMessage,
string? textContent = null,
ActivityTracingOptions? options = null)
{
if (activity == null)
{
return;
}
activity.SetTag(WireMockSemanticConventions.WebSocketMessageType, messageType.ToString());
activity.SetTag(WireMockSemanticConventions.WebSocketMessageSize, messageSize);
activity.SetTag(WireMockSemanticConventions.WebSocketEndOfMessage, endOfMessage);
// Record message content if enabled and it's text
if (options?.RecordRequestBody == true && messageType == WebSocketMessageType.Text && textContent != null)
{
activity.SetTag(WireMockSemanticConventions.WebSocketMessageContent, textContent);
}
activity.SetTag(WireMockSemanticConventions.OtelStatusCode, "OK");
}
}

View File

@@ -7,6 +7,8 @@ namespace WireMock.Owin.ActivityTracing;
/// </summary>
internal static class WireMockSemanticConventions
{
public const string OtelStatusCode = "otel.status_code";
// Standard HTTP semantic conventions (OpenTelemetry)
public const string HttpMethod = "http.request.method";
public const string HttpUrl = "url.full";
@@ -25,4 +27,10 @@ internal static class WireMockSemanticConventions
public const string RequestGuid = "wiremock.request.guid";
public const string RequestBody = "wiremock.request.body";
public const string ResponseBody = "wiremock.response.body";
// WebSocket-specific attributes
public const string WebSocketMessageType = "wiremock.websocket.message.type";
public const string WebSocketMessageSize = "wiremock.websocket.message.size";
public const string WebSocketEndOfMessage = "wiremock.websocket.message.end_of_message";
public const string WebSocketMessageContent = "wiremock.websocket.message.content";
}

View File

@@ -1,6 +1,6 @@
// Copyright © WireMock.Net
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
#if NET8_0
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using WireMock.Types;
@@ -9,6 +9,8 @@ namespace WireMock.Owin;
internal partial class AspNetCoreSelfHost
{
private const string CorsPolicyName = "WireMock.Net - Policy";
public void AddCors(IServiceCollection services)
{
if (_wireMockMiddlewareOptions.CorsPolicyOptions > CorsPolicyOptions.None)

View File

@@ -1,12 +1,8 @@
// Copyright © WireMock.Net
#if USE_ASPNETCORE && !NETSTANDARD1_3
using System;
using System.Collections.Generic;
using System.Net;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using CertificateLoader = WireMock.HttpsCertificate.CertificateLoader;
@@ -38,7 +34,7 @@ internal partial class AspNetCoreSelfHost
options.ServerCertificate = CertificateLoader.LoadCertificate(wireMockMiddlewareOptions, urlDetail.Host);
}
options.ClientCertificateMode = (ClientCertificateMode)wireMockMiddlewareOptions.ClientCertificateMode;
options.ClientCertificateMode = wireMockMiddlewareOptions.ClientCertificateMode;
if (wireMockMiddlewareOptions.AcceptAnyClientCertificate)
{
options.ClientCertificateValidation = (_, _, _) => true;
@@ -47,7 +43,7 @@ internal partial class AspNetCoreSelfHost
if (urlDetail.IsHttp2)
{
listenOptions.Protocols = HttpProtocols.Http2;
SetHttp2AsProtocolsOnListenOptions(listenOptions);
}
});
continue;
@@ -55,10 +51,7 @@ internal partial class AspNetCoreSelfHost
if (urlDetail.IsHttp2)
{
Listen(kestrelOptions, urlDetail, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http2;
});
Listen(kestrelOptions, urlDetail, SetHttp2AsProtocolsOnListenOptions);
continue;
}
@@ -66,6 +59,15 @@ internal partial class AspNetCoreSelfHost
}
}
private static void SetHttp2AsProtocolsOnListenOptions(ListenOptions listenOptions)
{
#if NET8_0_OR_GREATER
listenOptions.Protocols = HttpProtocols.Http2;
#else
throw new NotSupportedException("HTTP/2 is only supported in .NET 8 or greater.");
#endif
}
private static void Listen(KestrelServerOptions kestrelOptions, HostUrlDetails urlDetail, Action<ListenOptions> configure)
{
// Listens on any IP with the given port.
@@ -111,5 +113,4 @@ internal static class IWebHostBuilderExtensions
services.Configure<KestrelServerOptions>(context.Configuration.GetSection("Kestrel"));
});
}
}
#endif
}

View File

@@ -1,57 +0,0 @@
// Copyright © WireMock.Net
#if USE_ASPNETCORE && NETSTANDARD1_3
using System.Collections.Generic;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using WireMock.HttpsCertificate;
namespace WireMock.Owin;
internal partial class AspNetCoreSelfHost
{
private static void SetKestrelOptionsLimits(KestrelServerOptions options)
{
options.Limits.MaxRequestBufferSize = null;
options.Limits.MaxRequestHeaderCount = 100;
options.Limits.MaxResponseBufferSize = null;
}
private static void SetHttpsAndUrls(KestrelServerOptions options, IWireMockMiddlewareOptions wireMockMiddlewareOptions, IEnumerable<HostUrlDetails> urlDetails)
{
foreach (var urlDetail in urlDetails)
{
if (urlDetail.IsHttps)
{
options.UseHttps(new HttpsConnectionFilterOptions
{
ServerCertificate = wireMockMiddlewareOptions.CustomCertificateDefined ? CertificateLoader.LoadCertificate(wireMockMiddlewareOptions, urlDetail.Host) : PublicCertificateHelper.GetX509Certificate2(),
ClientCertificateMode = (ClientCertificateMode) wireMockMiddlewareOptions.ClientCertificateMode,
ClientCertificateValidation = wireMockMiddlewareOptions.AcceptAnyClientCertificate ? (_, _, _) => true : null
});
}
}
}
}
internal static class IWebHostBuilderExtensions
{
internal static IWebHostBuilder ConfigureAppConfigurationUsingEnvironmentVariables(this IWebHostBuilder builder) => builder;
internal static IWebHostBuilder ConfigureKestrelServerOptions(this IWebHostBuilder builder)
{
var configuration = new ConfigurationBuilder()
.AddEnvironmentVariables()
.Build();
return builder.ConfigureServices(services =>
{
services.Configure<KestrelServerOptions>(configuration.GetSection("Kestrel"));
});
}
}
#endif

View File

@@ -1,42 +1,34 @@
// Copyright © WireMock.Net
#if USE_ASPNETCORE
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Stef.Validation;
using WireMock.Logging;
using WireMock.Owin.Mappers;
using WireMock.Serialization;
using WireMock.Services;
using WireMock.Util;
namespace WireMock.Owin;
internal partial class AspNetCoreSelfHost : IOwinSelfHost
internal partial class AspNetCoreSelfHost
{
private const string CorsPolicyName = "WireMock.Net - Policy";
private readonly CancellationTokenSource _cts = new();
private readonly IWireMockMiddlewareOptions _wireMockMiddlewareOptions;
private readonly IWireMockLogger _logger;
private readonly HostUrlOptions _urlOptions;
private Exception _runningException;
private IWebHost _host;
private IWebHost _host = null!;
public bool IsStarted { get; private set; }
public List<string> Urls { get; } = new();
public List<string> Urls { get; } = [];
public List<int> Ports { get; } = new();
public List<int> Ports { get; } = [];
public Exception RunningException => _runningException;
public Exception? RunningException { get; private set; }
public AspNetCoreSelfHost(IWireMockMiddlewareOptions wireMockMiddlewareOptions, HostUrlOptions urlOptions)
{
@@ -73,8 +65,11 @@ internal partial class AspNetCoreSelfHost : IOwinSelfHost
services.AddSingleton<IOwinRequestMapper, OwinRequestMapper>();
services.AddSingleton<IOwinResponseMapper, OwinResponseMapper>();
services.AddSingleton<IGuidUtils, GuidUtils>();
services.AddSingleton<IDateTimeUtils, DateTimeUtils>();
services.AddSingleton<LogEntryMapper>();
services.AddSingleton<IWireMockMiddlewareLogger, WireMockMiddlewareLogger>();
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
#if NET8_0_OR_GREATER
AddCors(services);
#endif
_wireMockMiddlewareOptions.AdditionalServiceRegistration?.Invoke(services);
@@ -83,8 +78,16 @@ internal partial class AspNetCoreSelfHost : IOwinSelfHost
{
appBuilder.UseMiddleware<GlobalExceptionMiddleware>();
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
#if NET8_0_OR_GREATER
UseCors(appBuilder);
var webSocketOptions = new WebSocketOptions();
if (_wireMockMiddlewareOptions.WebSocketSettings?.KeepAliveIntervalSeconds != null)
{
webSocketOptions.KeepAliveInterval = TimeSpan.FromSeconds(_wireMockMiddlewareOptions.WebSocketSettings.KeepAliveIntervalSeconds);
}
appBuilder.UseWebSockets(webSocketOptions);
#endif
_wireMockMiddlewareOptions.PreWireMockMiddlewareInit?.Invoke(appBuilder);
@@ -99,73 +102,76 @@ internal partial class AspNetCoreSelfHost : IOwinSelfHost
SetHttpsAndUrls(options, _wireMockMiddlewareOptions, _urlOptions.GetDetails());
})
.ConfigureKestrelServerOptions()
#if NETSTANDARD1_3
.UseUrls(_urlOptions.GetDetails().Select(u => u.Url).ToArray())
#endif
.Build();
return RunHost(_cts.Token);
}
private Task RunHost(CancellationToken token)
private Task RunHost(CancellationToken token)
{
try
{
try
{
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
var appLifetime = _host.Services.GetRequiredService<Microsoft.Extensions.Hosting.IHostApplicationLifetime>();
#if NET8_0_OR_GREATER
var appLifetime = _host.Services.GetRequiredService<Microsoft.Extensions.Hosting.IHostApplicationLifetime>();
#else
var appLifetime = _host.Services.GetRequiredService<IApplicationLifetime>();
var appLifetime = _host.Services.GetRequiredService<IApplicationLifetime>();
#endif
appLifetime.ApplicationStarted.Register(() =>
{
var addresses = _host.ServerFeatures
.Get<Microsoft.AspNetCore.Hosting.Server.Features.IServerAddressesFeature>()!
.Addresses;
appLifetime.ApplicationStarted.Register(() =>
{
var addresses = _host.ServerFeatures
.Get<Microsoft.AspNetCore.Hosting.Server.Features.IServerAddressesFeature>()!
.Addresses
.ToArray();
if (_urlOptions.Urls == null)
{
foreach (var address in addresses)
{
Urls.Add(address.Replace("0.0.0.0", "localhost").Replace("[::]", "localhost"));
PortUtils.TryExtract(address, out _, out _, out var scheme, out var host, out var port);
PortUtils.TryExtract(address, out _, out _, out _, out _, out var port);
var replacedHost = ReplaceHostWithLocalhost(host!);
var newUrl = $"{scheme}://{replacedHost}:{port}";
Urls.Add(newUrl);
Ports.Add(port);
}
}
else
{
var urlOptions = _urlOptions.Urls?.ToArray() ?? [];
for (int i = 0; i < urlOptions.Length; i++)
{
PortUtils.TryExtract(urlOptions[i], out _, out _, out var originalScheme, out _, out _);
if (originalScheme!.StartsWith("grpc", StringComparison.OrdinalIgnoreCase))
{
// Always replace "grpc" with "http" in the scheme because GrpcChannel needs http or https.
originalScheme = originalScheme.Replace("grpc", "http", StringComparison.OrdinalIgnoreCase);
}
PortUtils.TryExtract(addresses[i], out _, out _, out _, out var realHost, out var realPort);
var replacedHost = ReplaceHostWithLocalhost(realHost!);
var newUrl = $"{originalScheme}://{replacedHost}:{realPort}";
Urls.Add(newUrl);
Ports.Add(realPort);
}
}
IsStarted = true;
});
#if NETSTANDARD1_3
_logger.Info("Server using netstandard1.3");
#elif NETSTANDARD2_0
_logger.Info("Server using netstandard2.0");
#elif NETSTANDARD2_1
_logger.Info("Server using netstandard2.1");
#elif NETCOREAPP3_1
_logger.Info("Server using .NET Core App 3.1");
#elif NET5_0
_logger.Info("Server using .NET 5.0");
#elif NET6_0
_logger.Info("Server using .NET 6.0");
#elif NET7_0
_logger.Info("Server using .NET 7.0");
#elif NET8_0
#if NET8_0
_logger.Info("Server using .NET 8.0");
#elif NET46
_logger.Info("Server using .NET Framework 4.6.1 or higher");
#else
_logger.Info("Server using .NET Standard 2.0");
#endif
#if NETSTANDARD1_3
return Task.Run(() =>
{
_host.Run(token);
});
#else
return _host.RunAsync(token);
#endif
}
catch (Exception e)
{
_runningException = e;
RunningException = e;
_logger.Error(e.ToString());
IsStarted = false;
@@ -179,11 +185,11 @@ internal partial class AspNetCoreSelfHost : IOwinSelfHost
_cts.Cancel();
IsStarted = false;
#if NETSTANDARD1_3
return Task.CompletedTask;
#else
return _host.StopAsync();
#endif
}
}
#endif
private static string ReplaceHostWithLocalhost(string host)
{
return host.Replace("0.0.0.0", "localhost").Replace("[::]", "localhost");
}
}

View File

@@ -1,70 +1,41 @@
// Copyright © WireMock.Net
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
#if !USE_ASPNETCORE
using Microsoft.Owin;
using IContext = Microsoft.Owin.IOwinContext;
using OwinMiddleware = Microsoft.Owin.OwinMiddleware;
using Next = Microsoft.Owin.OwinMiddleware;
#else
using OwinMiddleware = System.Object;
using IContext = Microsoft.AspNetCore.Http.HttpContext;
using Next = Microsoft.AspNetCore.Http.RequestDelegate;
#endif
using WireMock.Owin.Mappers;
using Stef.Validation;
using WireMock.Owin.Mappers;
namespace WireMock.Owin
namespace WireMock.Owin;
internal class GlobalExceptionMiddleware
{
internal class GlobalExceptionMiddleware : OwinMiddleware
private readonly IWireMockMiddlewareOptions _options;
private readonly IOwinResponseMapper _responseMapper;
public GlobalExceptionMiddleware(RequestDelegate next, IWireMockMiddlewareOptions options, IOwinResponseMapper responseMapper)
{
private readonly IWireMockMiddlewareOptions _options;
private readonly IOwinResponseMapper _responseMapper;
Next = next;
_options = Guard.NotNull(options);
_responseMapper = Guard.NotNull(responseMapper);
}
#if !USE_ASPNETCORE
public GlobalExceptionMiddleware(Next next, IWireMockMiddlewareOptions options, IOwinResponseMapper responseMapper) : base(next)
public RequestDelegate Next { get; }
public Task Invoke(HttpContext ctx)
{
return InvokeInternalAsync(ctx);
}
private async Task InvokeInternalAsync(HttpContext ctx)
{
try
{
_options = Guard.NotNull(options);
_responseMapper = Guard.NotNull(responseMapper);;
await Next.Invoke(ctx).ConfigureAwait(false);
}
#else
public GlobalExceptionMiddleware(Next next, IWireMockMiddlewareOptions options, IOwinResponseMapper responseMapper)
catch (Exception ex)
{
Next = next;
_options = Guard.NotNull(options);
_responseMapper = Guard.NotNull(responseMapper);
}
#endif
#if USE_ASPNETCORE
public Next Next { get; }
#endif
#if !USE_ASPNETCORE
public override Task Invoke(IContext ctx)
#else
public Task Invoke(IContext ctx)
#endif
{
return InvokeInternalAsync(ctx);
}
private async Task InvokeInternalAsync(IContext ctx)
{
try
{
if (Next != null)
{
await Next.Invoke(ctx).ConfigureAwait(false);
}
}
catch (Exception ex)
{
_options.Logger.Error("HttpStatusCode set to 500 {0}", ex);
await _responseMapper.MapAsync(ResponseMessageBuilder.Create(500, JsonConvert.SerializeObject(ex)), ctx.Response).ConfigureAwait(false);
}
_options.Logger.Error("HttpStatusCode set to 500 {0}", ex);
await _responseMapper.MapAsync(ResponseMessageBuilder.Create(500, JsonConvert.SerializeObject(ex)), ctx.Response).ConfigureAwait(false);
}
}
}
}

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System.Collections.Generic;
using WireMock.Types;
using WireMock.Util;
@@ -23,20 +22,34 @@ internal class HostUrlOptions
var list = new List<HostUrlDetails>();
if (Urls == null)
{
if (HostingScheme is HostingScheme.Http or HostingScheme.Https)
if (HostingScheme is not HostingScheme.None)
{
var port = Port > 0 ? Port.Value : FindFreeTcpPort();
var scheme = HostingScheme == HostingScheme.Https ? "https" : "http";
list.Add(new HostUrlDetails { IsHttps = HostingScheme == HostingScheme.Https, IsHttp2 = UseHttp2 == true, Url = $"{scheme}://{Star}:{port}", Scheme = scheme, Host = Star, Port = port });
var scheme = GetSchemeAsString(HostingScheme);
var port = Port > 0 ? Port.Value : 0;
var isHttps = HostingScheme == HostingScheme.Https || HostingScheme == HostingScheme.Wss;
list.Add(new HostUrlDetails { IsHttps = isHttps, IsHttp2 = UseHttp2 == true, Url = $"{scheme}://{Star}:{port}", Scheme = scheme, Host = Star, Port = port });
}
if (HostingScheme == HostingScheme.HttpAndHttps)
{
var httpPort = Port > 0 ? Port.Value : FindFreeTcpPort();
list.Add(new HostUrlDetails { IsHttps = false, IsHttp2 = UseHttp2 == true, Url = $"http://{Star}:{httpPort}", Scheme = "http", Host = Star, Port = httpPort });
var port = Port > 0 ? Port.Value : 0;
var scheme = GetSchemeAsString(HostingScheme.Http);
list.Add(new HostUrlDetails { IsHttps = false, IsHttp2 = UseHttp2 == true, Url = $"{scheme}://{Star}:{port}", Scheme = scheme, Host = Star, Port = port });
var httpsPort = FindFreeTcpPort(); // In this scenario, always get a free port for https.
list.Add(new HostUrlDetails { IsHttps = true, IsHttp2 = UseHttp2 == true, Url = $"https://{Star}:{httpsPort}", Scheme = "https", Host = Star, Port = httpsPort });
var securePort = 0; // In this scenario, always get a free port for https.
var secureScheme = GetSchemeAsString(HostingScheme.Https);
list.Add(new HostUrlDetails { IsHttps = true, IsHttp2 = UseHttp2 == true, Url = $"{secureScheme}://{Star}:{securePort}", Scheme = secureScheme, Host = Star, Port = securePort });
}
if (HostingScheme == HostingScheme.WsAndWss)
{
var port = Port > 0 ? Port.Value : 0;
var scheme = GetSchemeAsString(HostingScheme.Ws);
list.Add(new HostUrlDetails { IsHttps = false, IsHttp2 = UseHttp2 == true, Url = $"{scheme}://{Star}:{port}", Scheme = scheme, Host = Star, Port = port });
var securePort = 0; // In this scenario, always get a free port for https.
var secureScheme = GetSchemeAsString(HostingScheme.Wss);
list.Add(new HostUrlDetails { IsHttps = true, IsHttp2 = UseHttp2 == true, Url = $"{secureScheme}://{Star}:{securePort}", Scheme = secureScheme, Host = Star, Port = securePort });
}
}
else
@@ -53,12 +66,19 @@ internal class HostUrlOptions
return list;
}
private static int FindFreeTcpPort()
private string GetSchemeAsString(HostingScheme scheme)
{
#if USE_ASPNETCORE || NETSTANDARD2_0 || NETSTANDARD2_1
return 0;
#else
return PortUtils.FindFreeTcpPort();
#endif
return scheme switch
{
HostingScheme.Http => "http",
HostingScheme.Https => "https",
HostingScheme.HttpAndHttps => "http", // Default to http when both are specified, since the https URL will be added separately with a free port.
HostingScheme.Ws => "ws",
HostingScheme.Wss => "wss",
HostingScheme.WsAndWss => "ws", // Default to ws when both are specified, since the wss URL will be added separately with a free port.
_ => throw new NotSupportedException($"Unsupported hosting scheme: {HostingScheme}")
};
}
}

View File

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

View File

@@ -0,0 +1,13 @@
// Copyright © WireMock.Net
using System.Diagnostics;
using WireMock.Logging;
namespace WireMock.Owin;
internal interface IWireMockMiddlewareLogger
{
void LogRequestAndResponse(bool logRequest, RequestMessage request, IResponseMessage? response, MappingMatcherResult? match, MappingMatcherResult? partialMatch, Activity? activity);
void LogLogEntry(LogEntry entry, bool addRequest);
}

View File

@@ -1,23 +1,17 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Concurrent;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using WireMock.Handlers;
using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Owin.ActivityTracing;
using WireMock.Settings;
using WireMock.Types;
using WireMock.Util;
using System.Security.Cryptography.X509Certificates;
using JetBrains.Annotations;
#if !USE_ASPNETCORE
using Owin;
#else
using IAppBuilder = Microsoft.AspNetCore.Builder.IApplicationBuilder;
using Microsoft.Extensions.DependencyInjection;
#endif
using WireMock.WebSockets;
using ClientCertificateMode = Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode;
namespace WireMock.Owin;
@@ -41,11 +35,10 @@ internal interface IWireMockMiddlewareOptions
int? MaxRequestLogCount { get; set; }
Action<IAppBuilder>? PreWireMockMiddlewareInit { get; set; }
Action<IApplicationBuilder>? PreWireMockMiddlewareInit { get; set; }
Action<IAppBuilder>? PostWireMockMiddlewareInit { get; set; }
Action<IApplicationBuilder>? PostWireMockMiddlewareInit { get; set; }
#if USE_ASPNETCORE
Action<IServiceCollection>? AdditionalServiceRegistration { get; set; }
CorsPolicyOptions? CorsPolicyOptions { get; set; }
@@ -53,7 +46,6 @@ internal interface IWireMockMiddlewareOptions
ClientCertificateMode ClientCertificateMode { get; set; }
bool AcceptAnyClientCertificate { get; set; }
#endif
IFileSystemHandler? FileSystemHandler { get; set; }
@@ -90,13 +82,21 @@ internal interface IWireMockMiddlewareOptions
QueryParameterMultipleValueSupport? QueryParameterMultipleValueSupport { get; set; }
public bool ProxyAll { get; set; }
bool ProxyAll { get; set; }
#if ACTIVITY_TRACING_SUPPORTED
/// <summary>
/// Gets or sets the activity tracing options.
/// When set, System.Diagnostics.Activity objects are created for request tracing.
/// </summary>
ActivityTracingOptions? ActivityTracingOptions { get; set; }
#endif
/// <summary>
/// The WebSocket connection registries per mapping (used for broadcast).
/// </summary>
ConcurrentDictionary<Guid, WebSocketConnectionRegistry> WebSocketRegistries { get; }
/// <summary>
/// WebSocket settings.
/// </summary>
WebSocketSettings? WebSocketSettings { get; set; }
}

View File

@@ -1,25 +1,19 @@
// Copyright © WireMock.Net
using System.Threading.Tasks;
#if !USE_ASPNETCORE
using IRequest = Microsoft.Owin.IOwinRequest;
#else
using IRequest = Microsoft.AspNetCore.Http.HttpRequest;
#endif
using Microsoft.AspNetCore.Http;
namespace WireMock.Owin.Mappers
namespace WireMock.Owin.Mappers;
/// <summary>
/// IOwinRequestMapper
/// </summary>
internal interface IOwinRequestMapper
{
/// <summary>
/// IOwinRequestMapper
/// MapAsync IRequest to RequestMessage
/// </summary>
internal interface IOwinRequestMapper
{
/// <summary>
/// MapAsync IRequest to RequestMessage
/// </summary>
/// <param name="request">The OwinRequest/HttpRequest</param>
/// <param name="options">The WireMockMiddlewareOptions</param>
/// <returns>RequestMessage</returns>
Task<RequestMessage> MapAsync(IRequest request, IWireMockMiddlewareOptions options);
}
/// <param name="context">The HttpContext</param>
/// <param name="options">The WireMockMiddlewareOptions</param>
/// <returns>RequestMessage</returns>
Task<RequestMessage> MapAsync(HttpContext context, IWireMockMiddlewareOptions options);
}

View File

@@ -1,11 +1,6 @@
// Copyright © WireMock.Net
using System.Threading.Tasks;
#if !USE_ASPNETCORE
using IResponse = Microsoft.Owin.IOwinResponse;
#else
using IResponse = Microsoft.AspNetCore.Http.HttpResponse;
#endif
using Microsoft.AspNetCore.Http;
namespace WireMock.Owin.Mappers;
@@ -18,6 +13,6 @@ internal interface IOwinResponseMapper
/// Map ResponseMessage to IResponse.
/// </summary>
/// <param name="responseMessage">The ResponseMessage</param>
/// <param name="response">The OwinResponse/HttpResponse</param>
Task MapAsync(IResponseMessage? responseMessage, IResponse response);
}
/// <param name="response">The HttpResponse</param>
Task MapAsync(IResponseMessage? responseMessage, HttpResponse response);
}

View File

@@ -1,18 +1,11 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using WireMock.Http;
using WireMock.Models;
using WireMock.Util;
#if !USE_ASPNETCORE
using IRequest = Microsoft.Owin.IOwinRequest;
#else
using Microsoft.AspNetCore.Http.Extensions;
using IRequest = Microsoft.AspNetCore.Http.HttpRequest;
#endif
namespace WireMock.Owin.Mappers;
@@ -22,8 +15,9 @@ namespace WireMock.Owin.Mappers;
internal class OwinRequestMapper : IOwinRequestMapper
{
/// <inheritdoc />
public async Task<RequestMessage> MapAsync(IRequest request, IWireMockMiddlewareOptions options)
public async Task<RequestMessage> MapAsync(HttpContext context, IWireMockMiddlewareOptions options)
{
var request = context.Request;
var (urlDetails, clientIP) = ParseRequest(request);
var method = request.Method;
@@ -73,22 +67,16 @@ internal class OwinRequestMapper : IOwinRequestMapper
body,
headers,
cookies,
httpVersion
#if USE_ASPNETCORE
, await request.HttpContext.Connection.GetClientCertificateAsync()
#endif
)
httpVersion,
await request.HttpContext.Connection.GetClientCertificateAsync()
)
{
DateTime = DateTime.UtcNow
};
}
private static (UrlDetails UrlDetails, string ClientIP) ParseRequest(IRequest request)
private static (UrlDetails UrlDetails, string ClientIP) ParseRequest(HttpRequest request)
{
#if !USE_ASPNETCORE
var urlDetails = UrlUtils.Parse(request.Uri, request.PathBase);
var clientIP = request.RemoteIpAddress;
#else
var urlDetails = UrlUtils.Parse(new Uri(request.GetEncodedUrl()), request.PathBase);
var connection = request.HttpContext.Connection;
@@ -105,7 +93,7 @@ internal class OwinRequestMapper : IOwinRequestMapper
{
clientIP = connection.RemoteIpAddress.ToString();
}
#endif
return (urlDetails, clientIP);
}
}

View File

@@ -1,29 +1,21 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using RandomDataGenerator.FieldOptions;
using RandomDataGenerator.Randomizers;
using Stef.Validation;
using WireMock.Http;
using WireMock.ResponseBuilders;
using WireMock.ResponseProviders;
using WireMock.Types;
using Stef.Validation;
using WireMock.Util;
#if !USE_ASPNETCORE
using IResponse = Microsoft.Owin.IOwinResponse;
#else
using Microsoft.AspNetCore.Http;
using IResponse = Microsoft.AspNetCore.Http.HttpResponse;
#endif
namespace WireMock.Owin.Mappers
{
/// <summary>
@@ -37,8 +29,8 @@ namespace WireMock.Owin.Mappers
private readonly Encoding _utf8NoBom = new UTF8Encoding(false);
// https://msdn.microsoft.com/en-us/library/78h415ay(v=vs.110).aspx
private static readonly IDictionary<string, Action<IResponse, bool, WireMockList<string>>> ResponseHeadersToFix =
new Dictionary<string, Action<IResponse, bool, WireMockList<string>>>(StringComparer.OrdinalIgnoreCase)
private static readonly IDictionary<string, Action<HttpResponse, bool, WireMockList<string>>> ResponseHeadersToFix =
new Dictionary<string, Action<HttpResponse, bool, WireMockList<string>>>(StringComparer.OrdinalIgnoreCase)
{
{ HttpKnownHeaderNames.ContentType, (r, _, v) => r.ContentType = v.FirstOrDefault() },
{ HttpKnownHeaderNames.ContentLength, (r, hasBody, v) =>
@@ -62,9 +54,9 @@ namespace WireMock.Owin.Mappers
}
/// <inheritdoc />
public async Task MapAsync(IResponseMessage? responseMessage, IResponse response)
public async Task MapAsync(IResponseMessage? responseMessage, HttpResponse response)
{
if (responseMessage == null)
if (responseMessage == null || responseMessage is WebSocketHandledResponse)
{
return;
}
@@ -128,7 +120,7 @@ namespace WireMock.Owin.Mappers
SetResponseTrailingHeaders(responseMessage, response);
}
private static async Task HandleSseStringAsync(IResponseMessage responseMessage, IResponse response, IBodyData bodyData)
private static async Task HandleSseStringAsync(IResponseMessage responseMessage, HttpResponse response, IBodyData bodyData)
{
if (bodyData.SseStringQueue == null)
{
@@ -202,7 +194,7 @@ namespace WireMock.Owin.Mappers
return null;
}
private static void SetResponseHeaders(IResponseMessage responseMessage, bool hasBody, IResponse response)
private static void SetResponseHeaders(IResponseMessage responseMessage, bool hasBody, HttpResponse response)
{
// Force setting the Date header (#577)
AppendResponseHeader(
@@ -218,7 +210,7 @@ namespace WireMock.Owin.Mappers
var value = item.Value;
if (ResponseHeadersToFix.TryGetValue(headerName, out var action))
{
action?.Invoke(response, hasBody, value);
action.Invoke(response, hasBody, value);
}
else
{
@@ -231,7 +223,7 @@ namespace WireMock.Owin.Mappers
}
}
private static void SetResponseTrailingHeaders(IResponseMessage responseMessage, IResponse response)
private static void SetResponseTrailingHeaders(IResponseMessage responseMessage, HttpResponse response)
{
if (responseMessage.TrailingHeaders == null)
{
@@ -239,13 +231,11 @@ namespace WireMock.Owin.Mappers
}
#if TRAILINGHEADERS
foreach (var item in responseMessage.TrailingHeaders)
foreach (var (headerName, value) in responseMessage.TrailingHeaders)
{
var headerName = item.Key;
var value = item.Value;
if (ResponseHeadersToFix.TryGetValue(headerName, out var action))
{
action?.Invoke(response, false, value);
action.Invoke(response, false, value);
}
else
{
@@ -259,13 +249,9 @@ namespace WireMock.Owin.Mappers
#endif
}
private static void AppendResponseHeader(IResponse response, string headerName, string[] values)
private static void AppendResponseHeader(HttpResponse response, string headerName, string[] values)
{
#if !USE_ASPNETCORE
response.Headers.AppendValues(headerName, values);
#else
response.Headers.Append(headerName, values);
#endif
}
}
}

View File

@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Linq;
using Stef.Validation;
using WireMock.Extensions;

View File

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

View File

@@ -1,415 +1,252 @@
// Copyright © WireMock.Net
using System;
using System.Threading.Tasks;
using System.Linq;
using System.Diagnostics;
using System.Net;
using Stef.Validation;
using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Http;
using WireMock.Owin.Mappers;
using WireMock.Serialization;
using WireMock.ResponseBuilders;
using WireMock.Settings;
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using WireMock.Constants;
using WireMock.Exceptions;
using WireMock.Util;
#if ACTIVITY_TRACING_SUPPORTED
using System.Diagnostics;
using WireMock.Http;
using WireMock.Matchers;
using WireMock.Owin.ActivityTracing;
#endif
#if !USE_ASPNETCORE
using IContext = Microsoft.Owin.IOwinContext;
using OwinMiddleware = Microsoft.Owin.OwinMiddleware;
using Next = Microsoft.Owin.OwinMiddleware;
#else
using OwinMiddleware = System.Object;
using IContext = Microsoft.AspNetCore.Http.HttpContext;
using Next = Microsoft.AspNetCore.Http.RequestDelegate;
#endif
using WireMock.Owin.Mappers;
using WireMock.ResponseBuilders;
using WireMock.Serialization;
using WireMock.Settings;
using WireMock.Util;
namespace WireMock.Owin
namespace WireMock.Owin;
internal class WireMockMiddleware(
#pragma warning disable CS9113 // Parameter is unread.
RequestDelegate next,
#pragma warning restore CS9113 // Parameter is unread.
IWireMockMiddlewareOptions options,
IOwinRequestMapper requestMapper,
IOwinResponseMapper responseMapper,
IMappingMatcher mappingMatcher,
IWireMockMiddlewareLogger logger,
IGuidUtils guidUtils,
IDateTimeUtils dateTimeUtils
)
{
internal class WireMockMiddleware : OwinMiddleware
private readonly object _lock = new();
public Task Invoke(HttpContext ctx)
{
private readonly object _lock = new();
private static readonly Task CompletedTask = Task.FromResult(false);
private readonly IWireMockMiddlewareOptions _options;
private readonly IOwinRequestMapper _requestMapper;
private readonly IOwinResponseMapper _responseMapper;
private readonly IMappingMatcher _mappingMatcher;
private readonly LogEntryMapper _logEntryMapper;
private readonly IGuidUtils _guidUtils;
#if !USE_ASPNETCORE
public WireMockMiddleware(
Next next,
IWireMockMiddlewareOptions options,
IOwinRequestMapper requestMapper,
IOwinResponseMapper responseMapper,
IMappingMatcher mappingMatcher,
IGuidUtils guidUtils
) : base(next)
if (options.HandleRequestsSynchronously.GetValueOrDefault(false))
{
_options = Guard.NotNull(options);
_requestMapper = Guard.NotNull(requestMapper);
_responseMapper = Guard.NotNull(responseMapper);
_mappingMatcher = Guard.NotNull(mappingMatcher);
_logEntryMapper = new LogEntryMapper(options);
_guidUtils = Guard.NotNull(guidUtils);
}
#else
public WireMockMiddleware(
Next next,
IWireMockMiddlewareOptions options,
IOwinRequestMapper requestMapper,
IOwinResponseMapper responseMapper,
IMappingMatcher mappingMatcher,
IGuidUtils guidUtils
)
{
_options = Guard.NotNull(options);
_requestMapper = Guard.NotNull(requestMapper);
_responseMapper = Guard.NotNull(responseMapper);
_mappingMatcher = Guard.NotNull(mappingMatcher);
_logEntryMapper = new LogEntryMapper(options);
_guidUtils = Guard.NotNull(guidUtils);
}
#endif
#if !USE_ASPNETCORE
public override Task Invoke(IContext ctx)
#else
public Task Invoke(IContext ctx)
#endif
{
if (_options.HandleRequestsSynchronously.GetValueOrDefault(false))
lock (_lock)
{
lock (_lock)
return InvokeInternalAsync(ctx);
}
}
return InvokeInternalAsync(ctx);
}
private async Task InvokeInternalAsync(HttpContext ctx)
{
// Store options in HttpContext for providers to access (e.g., WebSocketResponseProvider)
ctx.Items[nameof(IWireMockMiddlewareOptions)] = options;
ctx.Items[nameof(IWireMockMiddlewareLogger)] = logger;
ctx.Items[nameof(IGuidUtils)] = guidUtils;
ctx.Items[nameof(IDateTimeUtils)] = dateTimeUtils;
var request = await requestMapper.MapAsync(ctx, options).ConfigureAwait(false);
var logRequest = false;
IResponseMessage? response = null;
(MappingMatcherResult? Match, MappingMatcherResult? Partial) result = (null, null);
var tracingEnabled = options.ActivityTracingOptions is not null;
var excludeAdmin = options.ActivityTracingOptions?.ExcludeAdminRequests ?? true;
Activity? activity = null;
// Check if we should trace this request (optionally exclude admin requests)
var shouldTrace = tracingEnabled && !(excludeAdmin && request.Path.StartsWith("/__admin/"));
if (shouldTrace)
{
activity = WireMockActivitySource.StartRequestActivity(request.Method, request.Path);
WireMockActivitySource.EnrichWithRequest(activity, request, options.ActivityTracingOptions);
}
try
{
foreach (var mapping in options.Mappings.Values)
{
if (mapping.Scenario is null)
{
return InvokeInternalAsync(ctx);
continue;
}
// Set scenario start
if (!options.Scenarios.ContainsKey(mapping.Scenario) && mapping.IsStartState)
{
options.Scenarios.TryAdd(mapping.Scenario, new ScenarioState
{
Name = mapping.Scenario
});
}
}
return InvokeInternalAsync(ctx);
}
result = mappingMatcher.FindBestMatch(request);
private async Task InvokeInternalAsync(IContext ctx)
{
var request = await _requestMapper.MapAsync(ctx.Request, _options).ConfigureAwait(false);
#if ACTIVITY_TRACING_SUPPORTED
// Start activity if ActivityTracingOptions is configured
var tracingEnabled = _options.ActivityTracingOptions is not null;
var excludeAdmin = _options.ActivityTracingOptions?.ExcludeAdminRequests ?? true;
Activity? activity = null;
// Check if we should trace this request (optionally exclude admin requests)
var shouldTrace = tracingEnabled && !(excludeAdmin && request.Path.StartsWith("/__admin/"));
if (shouldTrace)
var targetMapping = result.Match?.Mapping;
if (targetMapping == null)
{
activity = WireMockActivitySource.StartRequestActivity(request.Method, request.Path);
WireMockActivitySource.EnrichWithRequest(activity, request, _options.ActivityTracingOptions);
logRequest = true;
options.Logger.Warn("HttpStatusCode set to 404 : No matching mapping found");
response = ResponseMessageBuilder.Create(HttpStatusCode.NotFound, WireMockConstants.NoMatchingFound);
return;
}
try
{
await InvokeInternalCoreAsync(ctx, request, activity).ConfigureAwait(false);
}
finally
{
activity?.Dispose();
}
#else
await InvokeInternalCoreAsync(ctx, request).ConfigureAwait(false);
#endif
}
logRequest = targetMapping.LogMapping;
#if ACTIVITY_TRACING_SUPPORTED
private async Task InvokeInternalCoreAsync(IContext ctx, RequestMessage request, Activity? activity)
#else
private async Task InvokeInternalCoreAsync(IContext ctx, RequestMessage request)
#endif
{
var logRequest = false;
IResponseMessage? response = null;
(MappingMatcherResult? Match, MappingMatcherResult? Partial) result = (null, null);
try
if (targetMapping.IsAdminInterface && options.AuthenticationMatcher != null && request.Headers != null)
{
foreach (var mapping in _options.Mappings.Values)
var authorizationHeaderPresent = request.Headers.TryGetValue(HttpKnownHeaderNames.Authorization, out var authorization);
if (!authorizationHeaderPresent)
{
if (mapping.Scenario is null)
{
continue;
}
// Set scenario start
if (!_options.Scenarios.ContainsKey(mapping.Scenario) && mapping.IsStartState)
{
_options.Scenarios.TryAdd(mapping.Scenario, new ScenarioState
{
Name = mapping.Scenario
});
}
}
result = _mappingMatcher.FindBestMatch(request);
var targetMapping = result.Match?.Mapping;
if (targetMapping == null)
{
logRequest = true;
_options.Logger.Warn("HttpStatusCode set to 404 : No matching mapping found");
response = ResponseMessageBuilder.Create(HttpStatusCode.NotFound, WireMockConstants.NoMatchingFound);
options.Logger.Error("HttpStatusCode set to 401, authorization header is missing.");
response = ResponseMessageBuilder.Create(HttpStatusCode.Unauthorized, null);
return;
}
logRequest = targetMapping.LogMapping;
if (targetMapping.IsAdminInterface && _options.AuthenticationMatcher != null && request.Headers != null)
var authorizationHeaderMatchResult = options.AuthenticationMatcher.IsMatch(authorization!.ToString());
if (!MatchScores.IsPerfect(authorizationHeaderMatchResult.Score))
{
var authorizationHeaderPresent = request.Headers.TryGetValue(HttpKnownHeaderNames.Authorization, out var authorization);
if (!authorizationHeaderPresent)
{
_options.Logger.Error("HttpStatusCode set to 401, authorization header is missing.");
response = ResponseMessageBuilder.Create(HttpStatusCode.Unauthorized, null);
return;
}
options.Logger.Error("HttpStatusCode set to 401, authentication failed.", authorizationHeaderMatchResult.Exception ?? throw new WireMockException("Authentication failed"));
response = ResponseMessageBuilder.Create(HttpStatusCode.Unauthorized, null);
return;
}
}
var authorizationHeaderMatchResult = _options.AuthenticationMatcher.IsMatch(authorization!.ToString());
if (!MatchScores.IsPerfect(authorizationHeaderMatchResult.Score))
{
_options.Logger.Error("HttpStatusCode set to 401, authentication failed.", authorizationHeaderMatchResult.Exception ?? throw new WireMockException("Authentication failed"));
response = ResponseMessageBuilder.Create(HttpStatusCode.Unauthorized, null);
return;
}
if (!targetMapping.IsAdminInterface && options.RequestProcessingDelay > TimeSpan.Zero)
{
await Task.Delay(options.RequestProcessingDelay.Value).ConfigureAwait(false);
}
var (theResponse, theOptionalNewMapping) = await targetMapping.ProvideResponseAsync(ctx, request).ConfigureAwait(false);
response = theResponse;
if (targetMapping.Provider is Response responseBuilder && !targetMapping.IsAdminInterface && theOptionalNewMapping != null)
{
if (responseBuilder?.ProxyAndRecordSettings?.SaveMapping == true || targetMapping.Settings.ProxyAndRecordSettings?.SaveMapping == true)
{
options.Mappings.TryAdd(theOptionalNewMapping.Guid, theOptionalNewMapping);
}
if (!targetMapping.IsAdminInterface && _options.RequestProcessingDelay > TimeSpan.Zero)
if (responseBuilder?.ProxyAndRecordSettings?.SaveMappingToFile == true || targetMapping.Settings.ProxyAndRecordSettings?.SaveMappingToFile == true)
{
await Task.Delay(_options.RequestProcessingDelay.Value).ConfigureAwait(false);
var matcherMapper = new MatcherMapper(targetMapping.Settings);
var mappingConverter = new MappingConverter(matcherMapper);
var mappingToFileSaver = new MappingToFileSaver(targetMapping.Settings, mappingConverter);
mappingToFileSaver.SaveMappingToFile(theOptionalNewMapping);
}
}
var (theResponse, theOptionalNewMapping) = await targetMapping.ProvideResponseAsync(request).ConfigureAwait(false);
response = theResponse;
if (targetMapping.Scenario != null)
{
UpdateScenarioState(targetMapping);
}
var responseBuilder = targetMapping.Provider as Response;
if (!targetMapping.IsAdminInterface && targetMapping.Webhooks?.Length > 0)
{
await SendToWebhooksAsync(targetMapping, request, response).ConfigureAwait(false);
}
}
catch (Exception ex)
{
options.Logger.Error($"Providing a Response for Mapping '{result.Match?.Mapping.Guid}' failed. HttpStatusCode set to 500. Exception: {ex}");
WireMockActivitySource.RecordException(activity, ex);
if (!targetMapping.IsAdminInterface && theOptionalNewMapping != null)
{
if (responseBuilder?.ProxyAndRecordSettings?.SaveMapping == true || targetMapping.Settings.ProxyAndRecordSettings?.SaveMapping == true)
{
_options.Mappings.TryAdd(theOptionalNewMapping.Guid, theOptionalNewMapping);
}
response = ResponseMessageBuilder.Create(500, ex.Message);
}
finally
{
logger.LogRequestAndResponse(logRequest, request, response, result.Match, result.Partial, activity);
if (responseBuilder?.ProxyAndRecordSettings?.SaveMappingToFile == true || targetMapping.Settings.ProxyAndRecordSettings?.SaveMappingToFile == true)
{
var matcherMapper = new MatcherMapper(targetMapping.Settings);
var mappingConverter = new MappingConverter(matcherMapper);
var mappingToFileSaver = new MappingToFileSaver(targetMapping.Settings, mappingConverter);
mappingToFileSaver.SaveMappingToFile(theOptionalNewMapping);
}
}
if (targetMapping.Scenario != null)
{
UpdateScenarioState(targetMapping);
}
if (!targetMapping.IsAdminInterface && targetMapping.Webhooks?.Length > 0)
{
await SendToWebhooksAsync(targetMapping, request, response).ConfigureAwait(false);
}
try
{
await responseMapper.MapAsync(response, ctx.Response).ConfigureAwait(false);
}
catch (Exception ex)
{
_options.Logger.Error($"Providing a Response for Mapping '{result.Match?.Mapping.Guid}' failed. HttpStatusCode set to 500. Exception: {ex}");
response = ResponseMessageBuilder.Create(500, ex.Message);
options.Logger.Error("HttpStatusCode set to 404 : No matching mapping found", ex);
#if ACTIVITY_TRACING_SUPPORTED
WireMockActivitySource.RecordException(activity, ex);
#endif
}
finally
{
var log = new LogEntry
{
Guid = _guidUtils.NewGuid(),
RequestMessage = request,
ResponseMessage = response,
MappingGuid = result.Match?.Mapping?.Guid,
MappingTitle = result.Match?.Mapping?.Title,
RequestMatchResult = result.Match?.RequestMatchResult,
PartialMappingGuid = result.Partial?.Mapping?.Guid,
PartialMappingTitle = result.Partial?.Mapping?.Title,
PartialMatchResult = result.Partial?.RequestMatchResult
};
#if ACTIVITY_TRACING_SUPPORTED
// Enrich activity with response and mapping info
WireMockActivitySource.EnrichWithLogEntry(activity, log, _options.ActivityTracingOptions);
#endif
LogRequest(log, logRequest);
try
{
if (_options.SaveUnmatchedRequests == true && result.Match?.RequestMatchResult is not { IsPerfectMatch: true })
{
var filename = $"{log.Guid}.LogEntry.json";
_options.FileSystemHandler?.WriteUnmatchedRequest(filename, JsonUtils.Serialize(log));
}
}
catch
{
// Empty catch
}
try
{
await _responseMapper.MapAsync(response, ctx.Response).ConfigureAwait(false);
}
catch (Exception ex)
{
_options.Logger.Error("HttpStatusCode set to 404 : No matching mapping found", ex);
var notFoundResponse = ResponseMessageBuilder.Create(HttpStatusCode.NotFound, WireMockConstants.NoMatchingFound);
await _responseMapper.MapAsync(notFoundResponse, ctx.Response).ConfigureAwait(false);
}
}
await CompletedTask.ConfigureAwait(false);
}
private async Task SendToWebhooksAsync(IMapping mapping, IRequestMessage request, IResponseMessage response)
{
var tasks = new List<Func<Task>>();
for (int index = 0; index < mapping.Webhooks?.Length; index++)
{
var httpClientForWebhook = HttpClientBuilder.Build(mapping.Settings.WebhookSettings ?? new WebhookSettings());
var webhookSender = new WebhookSender(mapping.Settings);
var webhookRequest = mapping.Webhooks[index].Request;
var webHookIndex = index;
tasks.Add(async () =>
{
try
{
var result = await webhookSender.SendAsync(httpClientForWebhook, mapping, webhookRequest, request, response).ConfigureAwait(false);
if (!result.IsSuccessStatusCode)
{
var content = await result.Content.ReadAsStringAsync().ConfigureAwait(false);
_options.Logger.Warn($"Sending message to Webhook [{webHookIndex}] from Mapping '{mapping.Guid}' failed. HttpStatusCode: {result.StatusCode} Content: {content}");
}
}
catch (Exception ex)
{
_options.Logger.Error($"Sending message to Webhook [{webHookIndex}] from Mapping '{mapping.Guid}' failed. Exception: {ex}");
}
});
}
if (mapping.UseWebhooksFireAndForget == true)
{
try
{
// Do not wait
await Task.Run(() =>
{
Task.WhenAll(tasks.Select(async task => await task.Invoke())).ConfigureAwait(false);
});
}
catch
{
// Ignore
}
}
else
{
await Task.WhenAll(tasks.Select(async task => await task.Invoke())).ConfigureAwait(false);
}
}
private void UpdateScenarioState(IMapping mapping)
{
var scenario = _options.Scenarios[mapping.Scenario!];
// Increase the number of times this state has been executed
scenario.Counter++;
// Only if the number of times this state is executed equals the required StateTimes, proceed to next state and reset the counter to 0
if (scenario.Counter == (mapping.TimesInSameState ?? 1))
{
scenario.NextState = mapping.NextState;
scenario.Counter = 0;
}
// Else just update Started and Finished
scenario.Started = true;
scenario.Finished = mapping.NextState == null;
}
private void LogRequest(LogEntry entry, bool addRequest)
{
_options.Logger.DebugRequestResponse(_logEntryMapper.Map(entry), entry.RequestMessage.Path.StartsWith("/__admin/"));
// If addRequest is set to true and MaxRequestLogCount is null or does have a value greater than 0, try to add a new request log.
if (addRequest && _options.MaxRequestLogCount is null or > 0)
{
TryAddLogEntry(entry);
}
// In case MaxRequestLogCount has a value greater than 0, try to delete existing request logs based on the count.
if (_options.MaxRequestLogCount is > 0)
{
var logEntries = _options.LogEntries.ToList();
foreach (var logEntry in logEntries.OrderBy(le => le.RequestMessage.DateTime).Take(logEntries.Count - _options.MaxRequestLogCount.Value))
{
TryRemoveLogEntry(logEntry);
}
}
// In case RequestLogExpirationDuration has a value greater than 0, try to delete existing request logs based on the date.
if (_options.RequestLogExpirationDuration is > 0)
{
var checkTime = DateTime.UtcNow.AddHours(-_options.RequestLogExpirationDuration.Value);
foreach (var logEntry in _options.LogEntries.ToList().Where(le => le.RequestMessage.DateTime < checkTime))
{
TryRemoveLogEntry(logEntry);
}
}
}
private void TryAddLogEntry(LogEntry logEntry)
{
try
{
_options.LogEntries.Add(logEntry);
}
catch
{
// Ignore exception (can happen during stress testing)
}
}
private void TryRemoveLogEntry(LogEntry logEntry)
{
try
{
_options.LogEntries.Remove(logEntry);
}
catch
{
// Ignore exception (can happen during stress testing)
var notFoundResponse = ResponseMessageBuilder.Create(HttpStatusCode.NotFound, WireMockConstants.NoMatchingFound);
await responseMapper.MapAsync(notFoundResponse, ctx.Response).ConfigureAwait(false);
}
}
}
}
private async Task SendToWebhooksAsync(IMapping mapping, IRequestMessage request, IResponseMessage response)
{
var tasks = new List<Func<Task>>();
for (int index = 0; index < mapping.Webhooks?.Length; index++)
{
var httpClientForWebhook = HttpClientBuilder.Build(mapping.Settings.WebhookSettings ?? new WebhookSettings());
var webhookSender = new WebhookSender(mapping.Settings);
var webhookRequest = mapping.Webhooks[index].Request;
var webHookIndex = index;
tasks.Add(async () =>
{
try
{
var result = await webhookSender.SendAsync(httpClientForWebhook, mapping, webhookRequest, request, response).ConfigureAwait(false);
if (!result.IsSuccessStatusCode)
{
var content = await result.Content.ReadAsStringAsync().ConfigureAwait(false);
options.Logger.Warn($"Sending message to Webhook [{webHookIndex}] from Mapping '{mapping.Guid}' failed. HttpStatusCode: {result.StatusCode} Content: {content}");
}
}
catch (Exception ex)
{
options.Logger.Error($"Sending message to Webhook [{webHookIndex}] from Mapping '{mapping.Guid}' failed. Exception: {ex}");
}
});
}
if (mapping.UseWebhooksFireAndForget == true)
{
try
{
// Do not wait
await Task.Run(() =>
{
Task.WhenAll(tasks.Select(async task => await task.Invoke())).ConfigureAwait(false);
});
}
catch
{
// Ignore
}
}
else
{
await Task.WhenAll(tasks.Select(async task => await task.Invoke())).ConfigureAwait(false);
}
}
private void UpdateScenarioState(IMapping mapping)
{
var scenario = options.Scenarios[mapping.Scenario!];
// Increase the number of times this state has been executed
scenario.Counter++;
// Only if the number of times this state is executed equals the required StateTimes, proceed to next state and reset the counter to 0
if (scenario.Counter == (mapping.TimesInSameState ?? 1))
{
scenario.NextState = mapping.NextState;
scenario.Counter = 0;
}
// Else just update Started and Finished
scenario.Started = true;
scenario.Finished = mapping.NextState == null;
}
}

View File

@@ -0,0 +1,114 @@
// Copyright © WireMock.Net
using System.Diagnostics;
using System.Linq;
using WireMock.Logging;
using WireMock.Owin.ActivityTracing;
using WireMock.Serialization;
using WireMock.Util;
namespace WireMock.Owin;
internal class WireMockMiddlewareLogger(
IWireMockMiddlewareOptions _options,
LogEntryMapper _logEntryMapper,
IGuidUtils _guidUtils
) : IWireMockMiddlewareLogger
{
public void LogRequestAndResponse(bool logRequest, RequestMessage request, IResponseMessage? response, MappingMatcherResult? match, MappingMatcherResult? partialMatch, Activity? activity)
{
var logEntry = new LogEntry
{
Guid = _guidUtils.NewGuid(),
RequestMessage = request,
ResponseMessage = response,
MappingGuid = match?.Mapping?.Guid,
MappingTitle = match?.Mapping?.Title,
RequestMatchResult = match?.RequestMatchResult,
PartialMappingGuid = partialMatch?.Mapping?.Guid,
PartialMappingTitle = partialMatch?.Mapping?.Title,
PartialMatchResult = partialMatch?.RequestMatchResult
};
WireMockActivitySource.EnrichWithLogEntry(activity, logEntry, _options.ActivityTracingOptions);
activity?.Dispose();
LogLogEntry(logEntry, logRequest);
try
{
if (_options.SaveUnmatchedRequests == true && match?.RequestMatchResult is not { IsPerfectMatch: true })
{
var filename = $"{logEntry.Guid}.LogEntry.json";
_options.FileSystemHandler?.WriteUnmatchedRequest(filename, JsonUtils.Serialize(logEntry));
}
}
catch
{
// Empty catch
}
}
public void LogLogEntry(LogEntry entry, bool addRequest)
{
_options.Logger.DebugRequestResponse(_logEntryMapper.Map(entry), entry.RequestMessage?.Path.StartsWith("/__admin/") == true);
// If addRequest is set to true and MaxRequestLogCount is null or does have a value greater than 0, try to add a new request log.
if (addRequest && _options.MaxRequestLogCount is null or > 0)
{
TryAddLogEntry(entry);
}
// In case MaxRequestLogCount has a value greater than 0, try to delete existing request logs based on the count.
if (_options.MaxRequestLogCount is > 0)
{
var logEntries = _options.LogEntries.ToList();
foreach (var logEntry in logEntries
.OrderBy(le => le.RequestMessage?.DateTime ?? le.ResponseMessage?.DateTime)
.Take(logEntries.Count - _options.MaxRequestLogCount.Value))
{
TryRemoveLogEntry(logEntry);
}
}
// In case RequestLogExpirationDuration has a value greater than 0, try to delete existing request logs based on the date.
if (_options.RequestLogExpirationDuration is > 0)
{
var logEntries = _options.LogEntries.ToList();
var checkTime = DateTime.UtcNow.AddHours(-_options.RequestLogExpirationDuration.Value);
foreach (var logEntry in logEntries.Where(le => le.RequestMessage?.DateTime < checkTime || le.ResponseMessage?.DateTime < checkTime))
{
TryRemoveLogEntry(logEntry);
}
}
}
private void TryAddLogEntry(LogEntry logEntry)
{
try
{
_options.LogEntries.Add(logEntry);
}
catch
{
// Ignore exception (can happen during stress testing)
}
}
private void TryRemoveLogEntry(LogEntry logEntry)
{
try
{
_options.LogEntries.Remove(logEntry);
}
catch
{
// Ignore exception (can happen during stress testing)
}
}
}

View File

@@ -1,27 +1,23 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Concurrent;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using WireMock.Handlers;
using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Owin.ActivityTracing;
using WireMock.Settings;
using WireMock.Types;
using WireMock.Util;
using System.Security.Cryptography.X509Certificates;
#if !USE_ASPNETCORE
using Owin;
#else
using IAppBuilder = Microsoft.AspNetCore.Builder.IApplicationBuilder;
using Microsoft.Extensions.DependencyInjection;
#endif
using WireMock.WebSockets;
using ClientCertificateMode = Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode;
namespace WireMock.Owin;
internal class WireMockMiddlewareOptions : IWireMockMiddlewareOptions
{
public IWireMockLogger Logger { get; set; }
public IWireMockLogger Logger { get; set; } = new WireMockConsoleLogger();
public TimeSpan? RequestProcessingDelay { get; set; }
@@ -39,11 +35,10 @@ internal class WireMockMiddlewareOptions : IWireMockMiddlewareOptions
public int? MaxRequestLogCount { get; set; }
public Action<IAppBuilder>? PreWireMockMiddlewareInit { get; set; }
public Action<IApplicationBuilder>? PreWireMockMiddlewareInit { get; set; }
public Action<IAppBuilder>? PostWireMockMiddlewareInit { get; set; }
public Action<IApplicationBuilder>? PostWireMockMiddlewareInit { get; set; }
#if USE_ASPNETCORE
public Action<IServiceCollection>? AdditionalServiceRegistration { get; set; }
public CorsPolicyOptions? CorsPolicyOptions { get; set; }
@@ -52,7 +47,6 @@ internal class WireMockMiddlewareOptions : IWireMockMiddlewareOptions
/// <inheritdoc />
public bool AcceptAnyClientCertificate { get; set; }
#endif
/// <inheritdoc cref="IWireMockMiddlewareOptions.FileSystemHandler"/>
public IFileSystemHandler? FileSystemHandler { get; set; }
@@ -108,8 +102,11 @@ internal class WireMockMiddlewareOptions : IWireMockMiddlewareOptions
/// <inheritdoc />
public bool ProxyAll { get; set; }
#if ACTIVITY_TRACING_SUPPORTED
/// <inheritdoc />
public ActivityTracingOptions? ActivityTracingOptions { get; set; }
#endif
/// <inheritdoc />
public ConcurrentDictionary<Guid, WebSocketConnectionRegistry> WebSocketRegistries { get; } = new();
public WebSocketSettings? WebSocketSettings { get; set; }
}

View File

@@ -1,8 +1,6 @@
// Copyright © WireMock.Net
using System;
using Stef.Validation;
using WireMock.Owin.ActivityTracing;
using WireMock.Settings;
namespace WireMock.Owin;
@@ -19,6 +17,7 @@ internal static class WireMockMiddlewareOptionsHelper
options ??= new WireMockMiddlewareOptions();
options.ActivityTracingOptions = settings.ActivityTracingOptions;
options.AllowBodyForAllHttpMethods = settings.AllowBodyForAllHttpMethods;
options.AllowOnlyDefinedHttpStatusCodeInResponse = settings.AllowOnlyDefinedHttpStatusCodeInResponse;
options.AllowPartialMapping = settings.AllowPartialMapping;
@@ -35,20 +34,6 @@ internal static class WireMockMiddlewareOptionsHelper
options.RequestLogExpirationDuration = settings.RequestLogExpirationDuration;
options.SaveUnmatchedRequests = settings.SaveUnmatchedRequests;
// Validate and configure activity tracing
ActivityTracingValidator.ValidateActivityApiPresence(settings);
#if ACTIVITY_TRACING_SUPPORTED
if (settings.ActivityTracingOptions is not null)
{
options.ActivityTracingOptions = new Owin.ActivityTracing.ActivityTracingOptions
{
ExcludeAdminRequests = settings.ActivityTracingOptions.ExcludeAdminRequests,
RecordRequestBody = settings.ActivityTracingOptions.RecordRequestBody,
RecordResponseBody = settings.ActivityTracingOptions.RecordResponseBody,
RecordMatchDetails = settings.ActivityTracingOptions.RecordMatchDetails
};
}
#endif
#if USE_ASPNETCORE
options.AdditionalServiceRegistration = settings.AdditionalServiceRegistration;
options.CorsPolicyOptions = settings.CorsPolicyOptions;

View File

@@ -5,11 +5,11 @@ namespace WireMock.Pact.Models.V2;
public class Interaction
{
public string? Description { get; set; }
public required string Description { get; set; }
public string? ProviderState { get; set; }
public PactRequest Request { get; set; } = new();
public required PactRequest Request { get; set; }
public PactResponse Response { get; set; } = new();
public required PactResponse Response { get; set; }
}

View File

@@ -8,20 +8,20 @@ public class MatchingRule
/// <summary>
/// type or regex
/// </summary>
public string Match { get; set; } = "type";
public required string Match { get; set; }
/// <summary>
/// Used for Match = "type"
/// </summary>
public string Min { get; set; }
public string? Min { get; set; }
/// <summary>
/// Used for Match = "type"
/// </summary>
public string Max { get; set; }
public string? Max { get; set; }
/// <summary>
/// Used for Match = "regex"
/// </summary>
public string Regex { get; set; }
public string? Regex { get; set; }
}

View File

@@ -5,7 +5,7 @@ namespace WireMock.Pact.Models.V2;
public class Metadata
{
public string PactSpecificationVersion { get; set; }
public required string PactSpecificationVersion { get; set; }
public PactSpecification PactSpecification { get; set; } = new PactSpecification();
public required PactSpecification PactSpecification { get; set; }
}

View File

@@ -1,17 +1,15 @@
// Copyright © WireMock.Net
#pragma warning disable CS1591
using System.Collections.Generic;
namespace WireMock.Pact.Models.V2;
public class Pact
{
public Pacticipant Consumer { get; set; }
public required Pacticipant Consumer { get; set; }
public List<Interaction> Interactions { get; set; } = new List<Interaction>();
public required List<Interaction> Interactions { get; set; } = [];
public Metadata Metadata { get; set; }
public Metadata? Metadata { get; set; }
public Pacticipant Provider { get; set; }
public required Pacticipant Provider { get; set; }
}

View File

@@ -1,7 +1,6 @@
// Copyright © WireMock.Net
#pragma warning disable CS1591
using System.Collections.Generic;
using WireMock.Constants;
namespace WireMock.Pact.Models.V2;
@@ -10,9 +9,9 @@ public class PactRequest
{
public IDictionary<string, string>? Headers { get; set; }
public string Method { get; set; } = HttpRequestMethod.GET;
public required string Method { get; set; } = HttpRequestMethod.GET;
public string? Path { get; set; } = "/";
public required string Path { get; set; } = "/";
public string? Query { get; set; }

View File

@@ -1,8 +1,6 @@
// Copyright © WireMock.Net
#pragma warning disable CS1591
using System.Collections.Generic;
namespace WireMock.Pact.Models.V2;
public class PactResponse
@@ -11,5 +9,5 @@ public class PactResponse
public IDictionary<string, string>? Headers { get; set; }
public int Status { get; set; } = 200;
public required int Status { get; set; }
}

View File

@@ -5,9 +5,9 @@ namespace WireMock.Pact.Models.V2;
public class PactRust
{
public string Ffi { get; set; }
public required string Ffi { get; set; }
public string Mockserver { get; set; }
public required string Mockserver { get; set; }
public string Models { get; set; }
public required string Models { get; set; }
}

View File

@@ -5,5 +5,5 @@ namespace WireMock.Pact.Models.V2;
public class PactSpecification
{
public string Version { get; set; } = "2.0";
public required string Version { get; set; } = "2.0";
}

View File

@@ -5,5 +5,5 @@ namespace WireMock.Pact.Models.V2;
public class Pacticipant
{
public string Name { get; set; }
public required string Name { get; set; }
}

View File

@@ -1,13 +1,11 @@
// Copyright © WireMock.Net
#pragma warning disable CS1591
using System.Collections.Generic;
namespace WireMock.Pact.Models.V2;
public class ProviderState
{
public string Name { get; set; }
public required string Name { get; set; }
public IDictionary<string, string> Params { get; set; }
public IDictionary<string, string>? Params { get; set; }
}

View File

@@ -1,9 +1,7 @@
// Copyright © WireMock.Net
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Stef.Validation;
using WireMock.Http;
using WireMock.Matchers;

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using WireMock.Settings;
using WireMock.Transformers;

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using Stef.Validation;
using WireMock.Matchers;
using WireMock.Matchers.Request;

View File

@@ -2,8 +2,6 @@
// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License.
// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root.
using System;
using System.Collections.Generic;
using Stef.Validation;
using WireMock.Matchers;
using WireMock.Matchers.Request;

View File

@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using Stef.Validation;

View File

@@ -2,8 +2,6 @@
// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License.
// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root.
using System;
using System.Collections.Generic;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using Stef.Validation;

View File

@@ -2,8 +2,6 @@
// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License.
// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root.
using System;
using System.Collections.Generic;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.Types;

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using Stef.Validation;
using WireMock.Matchers;
using WireMock.Matchers.Request;

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using Stef.Validation;
using WireMock.Matchers;
using WireMock.Matchers.Request;

View File

@@ -0,0 +1,48 @@
// Copyright © WireMock.Net
using System.Linq;
using WireMock.Matchers;
using WireMock.Matchers.Request;
namespace WireMock.RequestBuilders;
public partial class Request
{
/// <inheritdoc />
public bool IsWebSocket { get; private set; }
/// <inheritdoc />
public IRequestBuilder WithWebSocketUpgrade(params string[] protocols)
{
_requestMatchers.Add(new RequestMessageHeaderMatcher(
MatchBehaviour.AcceptOnMatch,
MatchOperator.Or,
"Upgrade",
true,
new ExactMatcher(true, "websocket")
));
_requestMatchers.Add(new RequestMessageHeaderMatcher(
MatchBehaviour.AcceptOnMatch,
MatchOperator.Or,
"Connection",
true,
new WildcardMatcher("*Upgrade*", true)
));
if (protocols.Length > 0)
{
_requestMatchers.Add(new RequestMessageHeaderMatcher(
MatchBehaviour.AcceptOnMatch,
MatchOperator.Or,
"Sec-WebSocket-Protocol",
true,
protocols.Select(p => new ExactMatcher(true, p)).ToArray()
));
}
IsWebSocket = true;
return this;
}
}

View File

@@ -2,8 +2,6 @@
// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License.
// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

View File

@@ -2,13 +2,10 @@
// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License.
// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
#if USE_ASPNETCORE
using System.Security.Cryptography.X509Certificates;
#endif
using System.Text.Json.Serialization;
using Stef.Validation;
using WireMock.Models;
using WireMock.Owin;
@@ -82,11 +79,10 @@ public class RequestMessage : IRequestMessage
/// <inheritdoc />
public byte[]? BodyAsBytes { get; }
#if MIMEKIT
/// <inheritdoc />
[Newtonsoft.Json.JsonIgnore] // Issue 1001
[JsonIgnore]
public Models.Mime.IMimeMessageData? BodyAsMimeMessage { get; }
#endif
/// <inheritdoc />
public string? DetectedBodyType { get; }
@@ -109,10 +105,8 @@ public class RequestMessage : IRequestMessage
/// <inheritdoc />
public string Origin { get; }
#if USE_ASPNETCORE
/// <inheritdoc />
public X509Certificate2? ClientCertificate { get; }
#endif
/// <summary>
/// Used for Unit Testing
@@ -135,10 +129,8 @@ public class RequestMessage : IRequestMessage
IBodyData? bodyData = null,
IDictionary<string, string[]>? headers = null,
IDictionary<string, string>? cookies = null,
string httpVersion = "1.1"
#if USE_ASPNETCORE
, X509Certificate2? clientCertificate = null
#endif
string httpVersion = "1.1",
X509Certificate2? clientCertificate = null
)
{
Guard.NotNull(urlDetails);
@@ -178,16 +170,11 @@ public class RequestMessage : IRequestMessage
Query = QueryStringParser.Parse(RawQuery, options?.QueryParameterMultipleValueSupport);
QueryIgnoreCase = new Dictionary<string, WireMockList<string>>(Query, StringComparer.OrdinalIgnoreCase);
#if USE_ASPNETCORE
ClientCertificate = clientCertificate;
#endif
#if MIMEKIT
if (TypeLoader.TryLoadStaticInstance<IMimeKitUtils>(out var mimeKitUtils) && mimeKitUtils.TryGetMimeMessage(this, out var mimeMessage))
{
BodyAsMimeMessage = mimeMessage;
}
#endif
}
/// <inheritdoc />
@@ -199,7 +186,6 @@ public class RequestMessage : IRequestMessage
}
var query = !ignoreCase ? Query : new Dictionary<string, WireMockList<string>>(Query, StringComparer.OrdinalIgnoreCase);
return query.ContainsKey(key) ? query[key] : null;
return query.TryGetValue(key, out var value) ? value : null;
}
}

View File

@@ -1,8 +1,6 @@
// Copyright © WireMock.Net
using System;
using System.Text;
using System.Threading.Tasks;
using JsonConverter.Abstractions;
using Stef.Validation;
using WireMock.Models;

View File

@@ -1,8 +1,6 @@
// Copyright © WireMock.Net
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Stef.Validation;
namespace WireMock.ResponseBuilders;

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System.Collections.Generic;
using System.Linq;
using Stef.Validation;
using WireMock.Types;

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using WireMock.Types;
namespace WireMock.ResponseBuilders;

View File

@@ -0,0 +1,48 @@
// Copyright © WireMock.Net
using WireMock.Settings;
using WireMock.WebSockets;
namespace WireMock.ResponseBuilders;
public partial class Response
{
/// <summary>
/// Internal property to store WebSocket builder configuration
/// </summary>
internal WebSocketBuilder? WebSocketBuilder { get; set; }
/// <summary>
/// Configure WebSocket response behavior
/// </summary>
public IResponseBuilder WithWebSocket(Action<IWebSocketBuilder> configure)
{
var builder = new WebSocketBuilder(this);
configure(builder);
WebSocketBuilder = builder;
return this;
}
/// <summary>
/// Proxy WebSocket to another server
/// </summary>
public IResponseBuilder WithWebSocketProxy(string targetUrl)
{
return WithWebSocketProxy(new ProxyAndRecordSettings { Url = targetUrl });
}
/// <summary>
/// Proxy WebSocket to another server with settings
/// </summary>
public IResponseBuilder WithWebSocketProxy(ProxyAndRecordSettings settings)
{
var builder = new WebSocketBuilder(this);
builder.WithProxy(settings);
WebSocketBuilder = builder;
return this;
}
}

View File

@@ -2,18 +2,14 @@
// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License.
// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root.
using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Http;
using Stef.Validation;
using WireMock.Proxy;
using WireMock.RequestBuilders;
using WireMock.Settings;
using WireMock.Transformers;
using WireMock.Transformers.Handlebars;
using WireMock.Transformers.Scriban;
using WireMock.Types;
using WireMock.Util;
@@ -187,8 +183,10 @@ public partial class Response : IResponseBuilder
}
/// <inheritdoc />
public async Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IMapping mapping, IRequestMessage requestMessage, WireMockServerSettings settings)
public async Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IMapping mapping, HttpContext context, IRequestMessage requestMessage, WireMockServerSettings settings)
{
Guard.NotNull(mapping);
Guard.NotNull(context);
Guard.NotNull(requestMessage);
Guard.NotNull(settings);

View File

@@ -2,12 +2,11 @@
// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License.
// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root.
using System.Collections.Generic;
using System.Linq;
using Stef.Validation;
using WireMock.ResponseBuilders;
using WireMock.Types;
using WireMock.Util;
using Stef.Validation;
namespace WireMock;
@@ -40,6 +39,12 @@ public class ResponseMessage : IResponseMessage
/// <inheritdoc cref="IResponseMessage.FaultPercentage" />
public double? FaultPercentage { get; set; }
/// <inheritdoc />
public DateTime DateTime { get; set; }
/// <inheritdoc />
public string? Method { get; set; }
/// <inheritdoc />
public void AddHeader(string name, string value)
{

View File

@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Net;
using WireMock.Admin.Mappings;
using WireMock.Constants;

View File

@@ -1,7 +1,6 @@
// Copyright © WireMock.Net
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Stef.Validation;
using WireMock.Settings;
@@ -9,15 +8,16 @@ namespace WireMock.ResponseProviders;
internal class DynamicAsyncResponseProvider : IResponseProvider
{
private readonly Func<IRequestMessage, Task<IResponseMessage>> _responseMessageFunc;
private readonly Func<HttpContext, IRequestMessage, Task<IResponseMessage>> _responseMessageFunc;
public DynamicAsyncResponseProvider(Func<IRequestMessage, Task<IResponseMessage>> responseMessageFunc)
public DynamicAsyncResponseProvider(Func<HttpContext, IRequestMessage, Task<IResponseMessage>> responseMessageFunc)
{
_responseMessageFunc = Guard.NotNull(responseMessageFunc);
}
public async Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IMapping mapping, IRequestMessage requestMessage, WireMockServerSettings settings)
/// <inheritdoc />
public async Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IMapping mapping, HttpContext context, IRequestMessage requestMessage, WireMockServerSettings settings)
{
return (await _responseMessageFunc(requestMessage).ConfigureAwait(false), null);
return (await _responseMessageFunc(context, requestMessage).ConfigureAwait(false), null);
}
}

Some files were not shown because too many files have changed in this diff Show More