Compare commits

..

43 Commits
1.25.0 ... ws3

Author SHA1 Message Date
Stef Heyenrath
c1cf61862e tests 2026-02-10 19:11:14 +01:00
Stef Heyenrath
ceab19514f - 2026-02-10 18:21:00 +01:00
Stef Heyenrath
fc24eaf376 remove IOwin 2026-02-10 18:17:20 +01:00
Stef Heyenrath
4f6d4a01b8 ... 2026-02-10 18:03:44 +01:00
Stef Heyenrath
1d2b22545b Add tests 2026-02-10 07:39:06 +01:00
Stef Heyenrath
f53afec823 more tests 2026-02-10 06:23:56 +01:00
Stef Heyenrath
2c1ab7e1d6 fix 2026-02-09 21:33:03 +01:00
Stef Heyenrath
7cffd98cb6 Add tests 2026-02-09 21:25:49 +01:00
Stef Heyenrath
39b09ccb44 Add WebSockets 2026-02-09 21:09:58 +01:00
Stef Heyenrath
dff55e175b HttpContext 2026-02-08 19:19:19 +01:00
Stef Heyenrath
88df9af9df Merge branch 'master' into version-2.x 2026-02-07 10:29:48 +01:00
Stef Heyenrath
93ee9454a2 mm 2026-01-24 09:19:12 +01:00
Stef Heyenrath
ff26f95ded mm 2026-01-18 18:43:18 +01:00
Stef Heyenrath
276f329eed mm 2026-01-05 21:38:14 +01:00
Stef Heyenrath
3b3f3604f7 Merge branch 'master' into version-2.x 2025-12-25 13:57:44 +01:00
Stef Heyenrath
d311813851 Merge branch 'master' into version-2.x 2025-12-24 17:00:27 +01:00
Stef Heyenrath
c8e1c66bf2 Merge branch 'master' into version-2.x 2025-12-24 15:30:29 +01:00
Stef Heyenrath
76d6f92be2 mm 2025-12-24 12:18:53 +01:00
Stef Heyenrath
3f349cca4c WireMock.Net.NUnit 2025-12-21 10:17:13 +01:00
Stef Heyenrath
19b85c7856 Merge branch 'master' into version-2.x 2025-12-21 09:50:31 +01:00
Stef Heyenrath
da912be37e mm 2025-12-20 13:46:11 +01:00
Stef Heyenrath
ca9fceab67 mm 2025-12-20 12:10:45 +01:00
Stef Heyenrath
c662bb7ad5 mm 2025-12-19 17:56:26 +01:00
Stef Heyenrath
7d6fd35716 Merge branch 'master' into version-2.x 2025-12-12 11:19:09 +01:00
Stef Heyenrath
1b0b42d538 Merge branch 'master' into version-2.x 2025-12-08 19:16:51 +01:00
Stef Heyenrath
5f7b50a5b8 mm 2025-12-07 10:52:17 +01:00
Stef Heyenrath
2e37e822a8 Merge branch 'master' into version-2.x 2025-11-17 20:15:20 +01:00
Stef Heyenrath
1bc693512f HashSet 2025-10-22 12:26:39 +02:00
Stef Heyenrath
398dfe3eff mm 2025-10-22 11:01:42 +02:00
Stef Heyenrath
02228e5f8a mm 2025-10-06 17:08:28 +02:00
Stef Heyenrath
27d1a4b494 mm 2025-10-06 09:18:20 +02:00
Stef
3e170778c8 HttpClientBuilder / ssl3 2025-10-05 16:55:22 +02:00
Stef
9fa1f4081e mm 2025-10-05 15:19:18 +02:00
Stef Heyenrath
37e140b342 #endif 2025-09-07 19:51:42 +02:00
Stef Heyenrath
4fd3ee1dfd . 2025-08-31 12:51:33 +02:00
Stef Heyenrath
5b43a4f341 NO ConfigureAwait(false) + cleanup 2025-08-31 12:39:51 +02:00
Stef Heyenrath
0960d7cebd openapi 2025-08-31 12:21:21 +02:00
Stef Heyenrath
b111b019bc cleanup + fix tests for net8 2025-08-31 11:39:03 +02:00
Stef Heyenrath
bb561c94d0 cleanup some #if for NETSTANDARD1_3 2025-08-31 11:07:25 +02:00
Stef Heyenrath
5c221ff4fa 12 2025-08-31 10:03:51 +02:00
Stef Heyenrath
3077e7bee1 Setup .NET 9 2025-08-31 09:43:48 +02:00
Stef Heyenrath
ec54599827 mm + ... 2025-08-31 09:21:08 +02:00
Stef Heyenrath
034766a2d6 Version 2.x 2025-08-30 10:24:07 +02:00
229 changed files with 130662 additions and 2264 deletions

View File

@@ -19,6 +19,11 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup .NET 8
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: 'WireMock.Net.Tests'
run: dotnet test './test/WireMock.Net.Tests/WireMock.Net.Tests.csproj' -c Release --framework net8.0

View File

@@ -13,8 +13,9 @@
<RepositoryUrl>https://github.com/wiremock/WireMock.Net</RepositoryUrl>
<ApplicationIcon>../../resources/WireMock.Net-Logo.ico</ApplicationIcon>
<PackageReadmeFile>PackageReadme.md</PackageReadmeFile>
<LangVersion>12.0</LangVersion>
<LangVersion>12</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup Condition="'$(TF_BUILD)' == 'true'">
@@ -51,17 +52,17 @@
<ItemGroup>
<!-- CVE-2019-0820 -->
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
<!--<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />-->
<PackageReference Include="JetBrains.Annotations" Version="2024.3.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.15.0.120848">
<!--<PackageReference Include="SonarAnalyzer.CSharp" Version="10.15.0.120848">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</PackageReference>-->
<!-- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -68,14 +68,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Org.RestClient", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Org.Abstractions", "src\WireMock.Org.Abstractions\WireMock.Org.Abstractions.csproj", "{3BA5109E-5F30-4CC2-B699-02EC82560AA6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.WebApplication.NET6", "examples\WireMock.Net.WebApplication.NET6\WireMock.Net.WebApplication.NET6.csproj", "{3F7AA023-6833-4856-A08A-4B5717B592B8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.Proxy.NETCoreApp", "examples\WireMock.Net.Console.Proxy.NETCoreApp\WireMock.Net.Console.Proxy.NETCoreApp.csproj", "{670C7562-C154-442E-A249-7D26849BCD13}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.xUnit", "src\WireMock.Net.xUnit\WireMock.Net.xUnit.csproj", "{0DE0954F-8C00-4E8D-B94A-4361FC1CBE44}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.NET6.WithCertificate", "examples\WireMock.Net.Console.NET6.WithCertificate\WireMock.Net.Console.NET6.WithCertificate.csproj", "{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMockAzureQueueExample", "examples\WireMockAzureQueueExample\WireMockAzureQueueExample.csproj", "{BAA9EC2A-874B-45CE-8E51-A73622DC7F3D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Testcontainers", "src\WireMock.Net.Testcontainers\WireMock.Net.Testcontainers.csproj", "{12B016A5-9D8B-4EFE-96C2-CA51BE43367D}"
@@ -154,6 +150,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.OpenTelemetryD
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Console.MimePart", "examples\WireMock.Net.Console.MimePart\WireMock.Net.Console.MimePart.csproj", "{4005E20C-D42B-138A-79BE-B3F5420C563F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Console.NET8.WithCertificate", "examples\WireMock.Net.Console.NET8.WithCertificate\WireMock.Net.Console.NET8.WithCertificate.csproj", "{2D86546D-8A24-0A55-C962-2071BD299E05}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.WebApplication.IIS", "examples\WireMock.Net.WebApplication.IIS\WireMock.Net.WebApplication.IIS.csproj", "{5E6E9FA7-9135-7B82-2CCD-8CA87AC8043C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.WebSocketExamples", "examples\WireMock.Net.WebSocketExamples\WireMock.Net.WebSocketExamples.csproj", "{2CE8E3A6-59CC-FE9C-9399-AD54E1FA862B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -332,18 +334,6 @@ Global
{3BA5109E-5F30-4CC2-B699-02EC82560AA6}.Release|x64.Build.0 = Release|Any CPU
{3BA5109E-5F30-4CC2-B699-02EC82560AA6}.Release|x86.ActiveCfg = Release|Any CPU
{3BA5109E-5F30-4CC2-B699-02EC82560AA6}.Release|x86.Build.0 = Release|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Debug|x64.ActiveCfg = Debug|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Debug|x64.Build.0 = Debug|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Debug|x86.ActiveCfg = Debug|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Debug|x86.Build.0 = Debug|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Release|Any CPU.Build.0 = Release|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Release|x64.ActiveCfg = Release|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Release|x64.Build.0 = Release|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Release|x86.ActiveCfg = Release|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Release|x86.Build.0 = Release|Any CPU
{670C7562-C154-442E-A249-7D26849BCD13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{670C7562-C154-442E-A249-7D26849BCD13}.Debug|Any CPU.Build.0 = Debug|Any CPU
{670C7562-C154-442E-A249-7D26849BCD13}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -368,18 +358,6 @@ Global
{0DE0954F-8C00-4E8D-B94A-4361FC1CBE44}.Release|x64.Build.0 = Release|Any CPU
{0DE0954F-8C00-4E8D-B94A-4361FC1CBE44}.Release|x86.ActiveCfg = Release|Any CPU
{0DE0954F-8C00-4E8D-B94A-4361FC1CBE44}.Release|x86.Build.0 = Release|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Debug|x64.ActiveCfg = Debug|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Debug|x64.Build.0 = Debug|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Debug|x86.ActiveCfg = Debug|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Debug|x86.Build.0 = Debug|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Release|Any CPU.Build.0 = Release|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Release|x64.ActiveCfg = Release|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Release|x64.Build.0 = Release|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Release|x86.ActiveCfg = Release|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Release|x86.Build.0 = Release|Any CPU
{BAA9EC2A-874B-45CE-8E51-A73622DC7F3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BAA9EC2A-874B-45CE-8E51-A73622DC7F3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BAA9EC2A-874B-45CE-8E51-A73622DC7F3D}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -824,6 +802,42 @@ Global
{4005E20C-D42B-138A-79BE-B3F5420C563F}.Release|x64.Build.0 = Release|Any CPU
{4005E20C-D42B-138A-79BE-B3F5420C563F}.Release|x86.ActiveCfg = Release|Any CPU
{4005E20C-D42B-138A-79BE-B3F5420C563F}.Release|x86.Build.0 = Release|Any CPU
{2D86546D-8A24-0A55-C962-2071BD299E05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2D86546D-8A24-0A55-C962-2071BD299E05}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2D86546D-8A24-0A55-C962-2071BD299E05}.Debug|x64.ActiveCfg = Debug|Any CPU
{2D86546D-8A24-0A55-C962-2071BD299E05}.Debug|x64.Build.0 = Debug|Any CPU
{2D86546D-8A24-0A55-C962-2071BD299E05}.Debug|x86.ActiveCfg = Debug|Any CPU
{2D86546D-8A24-0A55-C962-2071BD299E05}.Debug|x86.Build.0 = Debug|Any CPU
{2D86546D-8A24-0A55-C962-2071BD299E05}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2D86546D-8A24-0A55-C962-2071BD299E05}.Release|Any CPU.Build.0 = Release|Any CPU
{2D86546D-8A24-0A55-C962-2071BD299E05}.Release|x64.ActiveCfg = Release|Any CPU
{2D86546D-8A24-0A55-C962-2071BD299E05}.Release|x64.Build.0 = Release|Any CPU
{2D86546D-8A24-0A55-C962-2071BD299E05}.Release|x86.ActiveCfg = Release|Any CPU
{2D86546D-8A24-0A55-C962-2071BD299E05}.Release|x86.Build.0 = Release|Any CPU
{5E6E9FA7-9135-7B82-2CCD-8CA87AC8043C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5E6E9FA7-9135-7B82-2CCD-8CA87AC8043C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5E6E9FA7-9135-7B82-2CCD-8CA87AC8043C}.Debug|x64.ActiveCfg = Debug|Any CPU
{5E6E9FA7-9135-7B82-2CCD-8CA87AC8043C}.Debug|x64.Build.0 = Debug|Any CPU
{5E6E9FA7-9135-7B82-2CCD-8CA87AC8043C}.Debug|x86.ActiveCfg = Debug|Any CPU
{5E6E9FA7-9135-7B82-2CCD-8CA87AC8043C}.Debug|x86.Build.0 = Debug|Any CPU
{5E6E9FA7-9135-7B82-2CCD-8CA87AC8043C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5E6E9FA7-9135-7B82-2CCD-8CA87AC8043C}.Release|Any CPU.Build.0 = Release|Any CPU
{5E6E9FA7-9135-7B82-2CCD-8CA87AC8043C}.Release|x64.ActiveCfg = Release|Any CPU
{5E6E9FA7-9135-7B82-2CCD-8CA87AC8043C}.Release|x64.Build.0 = Release|Any CPU
{5E6E9FA7-9135-7B82-2CCD-8CA87AC8043C}.Release|x86.ActiveCfg = Release|Any CPU
{5E6E9FA7-9135-7B82-2CCD-8CA87AC8043C}.Release|x86.Build.0 = Release|Any CPU
{2CE8E3A6-59CC-FE9C-9399-AD54E1FA862B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2CE8E3A6-59CC-FE9C-9399-AD54E1FA862B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2CE8E3A6-59CC-FE9C-9399-AD54E1FA862B}.Debug|x64.ActiveCfg = Debug|Any CPU
{2CE8E3A6-59CC-FE9C-9399-AD54E1FA862B}.Debug|x64.Build.0 = Debug|Any CPU
{2CE8E3A6-59CC-FE9C-9399-AD54E1FA862B}.Debug|x86.ActiveCfg = Debug|Any CPU
{2CE8E3A6-59CC-FE9C-9399-AD54E1FA862B}.Debug|x86.Build.0 = Debug|Any CPU
{2CE8E3A6-59CC-FE9C-9399-AD54E1FA862B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2CE8E3A6-59CC-FE9C-9399-AD54E1FA862B}.Release|Any CPU.Build.0 = Release|Any CPU
{2CE8E3A6-59CC-FE9C-9399-AD54E1FA862B}.Release|x64.ActiveCfg = Release|Any CPU
{2CE8E3A6-59CC-FE9C-9399-AD54E1FA862B}.Release|x64.Build.0 = Release|Any CPU
{2CE8E3A6-59CC-FE9C-9399-AD54E1FA862B}.Release|x86.ActiveCfg = Release|Any CPU
{2CE8E3A6-59CC-FE9C-9399-AD54E1FA862B}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -845,10 +859,8 @@ Global
{5B64F6CA-BF6B-4F67-BB2A-9C47E441703E} = {7EFB2C5B-1BB2-4AAF-BC9F-216ED80C594D}
{08B29DB1-FEFE-408A-AD0A-6BA6DDC8D70F} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{3BA5109E-5F30-4CC2-B699-02EC82560AA6} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{3F7AA023-6833-4856-A08A-4B5717B592B8} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{670C7562-C154-442E-A249-7D26849BCD13} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{0DE0954F-8C00-4E8D-B94A-4361FC1CBE44} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{BAA9EC2A-874B-45CE-8E51-A73622DC7F3D} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{12B016A5-9D8B-4EFE-96C2-CA51BE43367D} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{56A38798-C48B-4A4A-B805-071E05C02CE1} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
@@ -886,6 +898,9 @@ Global
{C8F4E6D2-9A3B-4F1C-8D5E-7A2B3C4D5E6F} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{9957038D-F9C3-CA5D-E8AE-BE188E512635} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{4005E20C-D42B-138A-79BE-B3F5420C563F} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{2D86546D-8A24-0A55-C962-2071BD299E05} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{5E6E9FA7-9135-7B82-2CCD-8CA87AC8043C} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{2CE8E3A6-59CC-FE9C-9399-AD54E1FA862B} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458}

View File

@@ -376,9 +376,9 @@ namespace WireMock.Net.ConsoleApplication
PreWireMockMiddlewareInit = app => { System.Console.WriteLine($"PreWireMockMiddlewareInit : {app.GetType()}"); },
PostWireMockMiddlewareInit = app => { System.Console.WriteLine($"PostWireMockMiddlewareInit : {app.GetType()}"); },
#if USE_ASPNETCORE
//#if USE_ASPNETCORE
AdditionalServiceRegistration = services => { System.Console.WriteLine($"AdditionalServiceRegistration : {services.GetType()}"); },
#endif
//#endif
Logger = new WireMockConsoleLogger(),
HandlebarsRegistrationCallback = (handlebarsContext, fileSystemHandler) =>
@@ -399,7 +399,7 @@ namespace WireMock.Net.ConsoleApplication
//var response = await http.GetAsync($"{_wireMockServer.Url}/pricing");
//var value = await response.Content.ReadAsStringAsync();
#if PROTOBUF
//#if PROTOBUF
var protoBufJsonMatcher = new JsonPartialWildcardMatcher(new { name = "*" });
server
.Given(Request.Create()
@@ -478,9 +478,9 @@ namespace WireMock.Net.ConsoleApplication
.WithTrailingHeader("grpc-status", "0")
.WithTransformer()
);
#endif
//#endif
#if GRAPHQL
//#if GRAPHQL
var customScalars = new Dictionary<string, Type> { { "MyCustomScalar", typeof(int) } };
server
.Given(Request.Create()
@@ -554,7 +554,7 @@ namespace WireMock.Net.ConsoleApplication
}
""")
);
#endif
//#endif
// 400 ms
server

View File

@@ -1,34 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<DefineConstants>$(DefineConstants);GRAPHQL;MIMEKIT;PROTOBUF</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<!--<DefineConstants>$(DefineConstants);GRAPHQL;MIMEKIT;PROTOBUF</DefineConstants>-->
</PropertyGroup>
<ItemGroup>
<Content Include="__admin\mappings\*.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="__admin\mappings\*.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Compile Remove="__admin\mappings\1.cs" />
</ItemGroup>
<ItemGroup>
<Compile Remove="__admin\mappings\1.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
<PackageReference Include="log4net" Version="2.0.15" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
<PackageReference Include="log4net" Version="2.0.15" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<None Update="log4net.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="nlog.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Update="log4net.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="nlog.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ApplicationIcon>../../resources/WireMock.Net-Logo.ico</ApplicationIcon>
</PropertyGroup>

View File

@@ -7,16 +7,16 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<!--<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
</ItemGroup>
</ItemGroup>-->
<ItemGroup>
<PackageReference Update="SonarAnalyzer.CSharp" Version="10.12.0.118525" />
</ItemGroup>
<!--<ItemGroup>
<PackageReference Include="WireMock.Net" Version="1.8.11" />
</ItemGroup>-->
<ItemGroup>
<PackageReference Include="WireMock.Net" Version="1.12.0" />
</ItemGroup>
</Project>

View File

@@ -6,6 +6,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="2.3.0" />
<ProjectReference Include="..\..\src\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
<ProjectReference Include="..\..\src\WireMock.Net.OpenApiParser\WireMock.Net.OpenApiParser.csproj" />
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />

View File

@@ -115,10 +115,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="log4net">
<Version>3.0.3</Version>
<Version>3.2.0</Version>
</PackageReference>
<PackageReference Include="WireMock.Net">
<Version>1.8.11</Version>
<Version>1.12.0</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<!--<RuntimeIdentifiers>win10-x64</RuntimeIdentifiers>-->
<StartupObject>WireMock.Net.WebApplication.Program</StartupObject>
<AssemblyName>WireMock.Net.WebApplication</AssemblyName>
@@ -11,7 +11,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

View File

@@ -0,0 +1,725 @@
// Copyright © WireMock.Net
using System;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WireMock.Logging;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Server;
using WireMock.Settings;
namespace WireMock.Net.WebSocketExamples;
public static class Program
{
public static async Task Main(string[] args)
{
Console.WriteLine("WireMock.Net WebSocket Examples");
Console.WriteLine("================================\n");
Console.WriteLine("Choose an example to run:");
Console.WriteLine("1. Echo Server");
Console.WriteLine("2. Custom Message Handler");
Console.WriteLine("3. Broadcast Server");
Console.WriteLine("4. Scenario/State Machine");
Console.WriteLine("5. WebSocket Proxy");
Console.WriteLine("6. Multiple WebSocket Endpoints");
Console.WriteLine("7. All Examples (runs all endpoints)");
Console.WriteLine("0. Exit\n");
Console.Write("Enter choice: ");
var choice = Console.ReadLine();
switch (choice)
{
case "1":
await RunEchoServerExample();
break;
case "2":
await RunCustomMessageHandlerExample();
break;
case "3":
await RunBroadcastExample();
break;
case "4":
await RunScenarioExample();
break;
case "5":
await RunProxyExample();
break;
case "6":
await RunMultipleEndpointsExample();
break;
case "7":
await RunAllExamples();
break;
case "0":
return;
default:
Console.WriteLine("Invalid choice");
break;
}
}
/// <summary>
/// Example 1: Simple Echo Server
/// Echoes back all messages received from the client
/// </summary>
private static async Task RunEchoServerExample()
{
Console.WriteLine("\n=== Echo Server Example ===");
Console.WriteLine("Starting WebSocket echo server...\n");
var server = WireMockServer.Start(new WireMockServerSettings
{
Port = 9091,
Logger = new WireMockConsoleLogger()
});
server
.Given(Request.Create()
.WithPath("/ws/echo")
.WithWebSocketUpgrade()
)
.RespondWith(Response.Create()
.WithWebSocket(ws => ws
.WithEcho()
)
);
Console.WriteLine($"Echo server listening at: {server.Urls[0]}/ws/echo");
Console.WriteLine("\nTest with a WebSocket client:");
Console.WriteLine(" wscat -c ws://localhost:9091/ws/echo");
Console.WriteLine("\nPress any key to test or CTRL+C to exit...");
Console.ReadKey();
// Test the echo server
await TestWebSocketEcho(server.Urls[0]);
Console.WriteLine("\nPress any key to stop server...");
Console.ReadKey();
server.Stop();
}
/// <summary>
/// Example 2: Custom Message Handler
/// Processes messages and sends custom responses
/// </summary>
private static async Task RunCustomMessageHandlerExample()
{
Console.WriteLine("\n=== Custom Message Handler Example ===");
Console.WriteLine("Starting WebSocket server with custom message handler...\n");
var server = WireMockServer.Start(new WireMockServerSettings
{
Port = 9091,
Logger = new WireMockConsoleLogger()
});
server
.Given(Request.Create()
.WithPath("/ws/chat")
.WithWebSocketUpgrade()
)
.RespondWith(Response.Create()
.WithWebSocket(ws => ws
.WithMessageHandler(async (message, context) =>
{
if (message.MessageType == WebSocketMessageType.Text)
{
var text = message.Text ?? string.Empty;
// Handle different commands
if (text.StartsWith("/help"))
{
await context.SendAsync("Available commands: /help, /time, /echo <text>, /upper <text>, /reverse <text>");
}
else if (text.StartsWith("/time"))
{
await context.SendAsync($"Server time: {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC");
}
else if (text.StartsWith("/echo "))
{
await context.SendAsync(text.Substring(6));
}
else if (text.StartsWith("/upper "))
{
await context.SendAsync(text.Substring(7).ToUpper());
}
else if (text.StartsWith("/reverse "))
{
var toReverse = text.Substring(9);
var reversed = new string(toReverse.Reverse().ToArray());
await context.SendAsync(reversed);
}
else if (text == "/quit")
{
await context.SendAsync("Goodbye!");
await context.CloseAsync(WebSocketCloseStatus.NormalClosure, "Client requested disconnect");
}
else
{
await context.SendAsync($"Unknown command: {text}. Type /help for available commands.");
}
}
})
)
);
Console.WriteLine($"Chat server listening at: {server.Urls[0]}/ws/chat");
Console.WriteLine("\nTest with:");
Console.WriteLine(" wscat -c ws://localhost:9091/ws/chat");
Console.WriteLine("\nThen try commands: /help, /time, /echo hello, /upper hello, /reverse hello");
Console.WriteLine("\nPress any key to test or CTRL+C to exit...");
Console.ReadKey();
await TestWebSocketChat(server.Urls[0]);
Console.WriteLine("\nPress any key to stop server...");
Console.ReadKey();
server.Stop();
}
/// <summary>
/// Example 3: Broadcast Server
/// Broadcasts messages to all connected clients
/// </summary>
private static async Task RunBroadcastExample()
{
Console.WriteLine("\n=== Broadcast Server Example ===");
Console.WriteLine("Starting WebSocket broadcast server...\n");
var server = WireMockServer.Start(new WireMockServerSettings
{
Port = 9091,
Logger = new WireMockConsoleLogger()
});
var broadcastMappingGuid = Guid.NewGuid();
server
.Given(Request.Create()
.WithPath("/ws/broadcast")
.WithWebSocketUpgrade()
)
.WithGuid(broadcastMappingGuid)
.RespondWith(Response.Create()
.WithWebSocket(ws => ws
.WithBroadcast()
.WithMessageHandler(async (message, context) =>
{
if (message.MessageType == WebSocketMessageType.Text)
{
var text = message.Text ?? string.Empty;
var timestamp = DateTime.UtcNow.ToString("HH:mm:ss");
var broadcastMessage = $"[{timestamp}] Broadcast: {text}";
// Broadcast to all connected clients
await context.BroadcastTextAsync(broadcastMessage);
Console.WriteLine($"Broadcasted to {server.GetWebSocketConnections(broadcastMappingGuid).Count} clients: {text}");
}
})
)
);
Console.WriteLine($"Broadcast server listening at: {server.Urls[0]}/ws/broadcast");
Console.WriteLine("\nConnect multiple clients:");
Console.WriteLine(" wscat -c ws://localhost:9091/ws/broadcast");
Console.WriteLine("\nMessages sent from any client will be broadcast to all clients");
Console.WriteLine("\nPress any key to stop server...");
Console.ReadKey();
server.Stop();
}
/// <summary>
/// Example 4: Scenario/State Machine
/// Demonstrates state transitions during WebSocket session
/// </summary>
private static async Task RunScenarioExample()
{
Console.WriteLine("\n=== Scenario/State Machine Example ===");
Console.WriteLine("Starting WebSocket server with scenario support...\n");
var server = WireMockServer.Start(new WireMockServerSettings
{
Port = 9091,
Logger = new WireMockConsoleLogger()
});
// Initial state: Waiting for players
server
.Given(Request.Create()
.WithPath("/ws/game")
.WithWebSocketUpgrade()
)
.InScenario("GameSession")
.WillSetStateTo("Lobby")
.RespondWith(Response.Create()
.WithWebSocket(ws => ws
.WithMessageHandler(async (msg, ctx) =>
{
await ctx.SendAsync("Welcome to the game lobby! Type 'ready' to start or 'quit' to leave.");
})
)
);
// Lobby state: Waiting for ready
server
.Given(Request.Create()
.WithPath("/ws/game")
.WithWebSocketUpgrade()
)
.InScenario("GameSession")
.WhenStateIs("Lobby")
.RespondWith(Response.Create()
.WithWebSocket(ws => ws
.WithMessageHandler(async (msg, ctx) =>
{
var text = msg.Text?.ToLower() ?? string.Empty;
if (text == "ready")
{
ctx.SetScenarioState("Playing");
await ctx.SendAsync("Game started! Type 'attack' to attack, 'defend' to defend, or 'quit' to exit.");
}
else if (text == "quit")
{
await ctx.SendAsync("You left the lobby. Goodbye!");
await ctx.CloseAsync(WebSocketCloseStatus.NormalClosure, "Player quit");
}
else
{
await ctx.SendAsync("In lobby. Type 'ready' to start or 'quit' to leave.");
}
})
)
);
// Playing state: Game is active
server
.Given(Request.Create()
.WithPath("/ws/game")
.WithWebSocketUpgrade()
)
.InScenario("GameSession")
.WhenStateIs("Playing")
.RespondWith(Response.Create()
.WithWebSocket(ws => ws
.WithMessageHandler(async (msg, ctx) =>
{
var text = msg.Text?.ToLower() ?? string.Empty;
if (text == "attack")
{
await ctx.SendAsync("You attacked! Critical hit! 💥");
}
else if (text == "defend")
{
await ctx.SendAsync("You defended! Shield up! 🛡️");
}
else if (text == "quit")
{
ctx.SetScenarioState("GameOver");
await ctx.SendAsync("Game over! Thanks for playing.");
await ctx.CloseAsync(WebSocketCloseStatus.NormalClosure, "Game ended");
}
else
{
await ctx.SendAsync("Unknown action. Type 'attack', 'defend', or 'quit'.");
}
})
)
);
Console.WriteLine($"Game server listening at: {server.Urls[0]}/ws/game");
Console.WriteLine("\nConnect and follow the game flow:");
Console.WriteLine(" wscat -c ws://localhost:9091/ws/game");
Console.WriteLine("\nGame flow: Lobby -> Type 'ready' -> Playing -> Type 'attack'/'defend' -> Type 'quit'");
Console.WriteLine("\nPress any key to stop server...");
Console.ReadKey();
server.Stop();
}
/// <summary>
/// Example 5: WebSocket Proxy
/// Proxies WebSocket connections to another server
/// </summary>
private static async Task RunProxyExample()
{
Console.WriteLine("\n=== WebSocket Proxy Example ===");
Console.WriteLine("Starting WebSocket proxy server...\n");
var server = WireMockServer.Start(new WireMockServerSettings
{
Port = 9091,
Logger = new WireMockConsoleLogger()
});
server
.Given(Request.Create()
.WithPath("/ws/proxy")
.WithWebSocketUpgrade()
)
.RespondWith(Response.Create()
.WithWebSocketProxy("ws://echo.websocket.org")
);
Console.WriteLine($"Proxy server listening at: {server.Urls[0]}/ws/proxy");
Console.WriteLine("Proxying to: ws://echo.websocket.org");
Console.WriteLine("\nTest with:");
Console.WriteLine(" wscat -c ws://localhost:9091/ws/proxy");
Console.WriteLine("\nPress any key to stop server...");
Console.ReadKey();
server.Stop();
}
/// <summary>
/// Example 6: Multiple WebSocket Endpoints
/// Demonstrates running multiple WebSocket endpoints simultaneously
/// </summary>
private static async Task RunMultipleEndpointsExample()
{
Console.WriteLine("\n=== Multiple WebSocket Endpoints Example ===");
Console.WriteLine("Starting server with multiple WebSocket endpoints...\n");
var server = WireMockServer.Start(new WireMockServerSettings
{
Port = 9091,
Logger = new WireMockConsoleLogger(),
WebSocketSettings = new WebSocketSettings
{
MaxConnections = 100,
KeepAliveIntervalSeconds = 30
}
});
// Endpoint 1: Echo
server
.Given(Request.Create()
.WithPath("/ws/echo")
.WithWebSocketUpgrade()
)
.RespondWith(Response.Create()
.WithWebSocket(ws => ws.WithEcho())
);
// Endpoint 2: Time service
server
.Given(Request.Create()
.WithPath("/ws/time")
.WithWebSocketUpgrade()
)
.RespondWith(Response.Create()
.WithWebSocket(ws => ws
.WithMessageHandler(async (msg, ctx) =>
{
await ctx.SendAsync($"Server time: {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC");
})
)
);
// Endpoint 3: JSON service
server
.Given(Request.Create()
.WithPath("/ws/json")
.WithWebSocketUpgrade()
)
.RespondWith(Response.Create()
.WithWebSocket(ws => ws
.WithMessageHandler(async (msg, ctx) =>
{
var response = new
{
timestamp = DateTime.UtcNow,
message = msg.Text,
length = msg.Text?.Length ?? 0,
type = msg.MessageType.ToString()
};
await ctx.SendAsJsonAsync(response);
})
)
);
// Endpoint 4: Protocol-specific
server
.Given(Request.Create()
.WithPath("/ws/protocol")
.WithWebSocketUpgrade("chat", "superchat")
)
.RespondWith(Response.Create()
.WithWebSocket(ws => ws
.WithAcceptProtocol("chat")
.WithMessageHandler(async (msg, ctx) =>
{
await ctx.SendAsync($"Using protocol: chat. Message: {msg.Text}");
})
)
);
Console.WriteLine("Available WebSocket endpoints:");
Console.WriteLine($" 1. Echo: {server.Urls[0]}/ws/echo");
Console.WriteLine($" 2. Time: {server.Urls[0]}/ws/time");
Console.WriteLine($" 3. JSON: {server.Urls[0]}/ws/json");
Console.WriteLine($" 4. Protocol: {server.Urls[0]}/ws/protocol");
Console.WriteLine("\nTest with wscat:");
Console.WriteLine(" wscat -c ws://localhost:9091/ws/echo");
Console.WriteLine(" wscat -c ws://localhost:9091/ws/time");
Console.WriteLine(" wscat -c ws://localhost:9091/ws/json");
Console.WriteLine(" wscat -c ws://localhost:9091/ws/protocol -s chat");
Console.WriteLine("\nPress any key to stop server...");
Console.ReadKey();
server.Stop();
}
/// <summary>
/// Example 7: Run All Examples
/// Starts a server with all example endpoints
/// </summary>
private static async Task RunAllExamples()
{
Console.WriteLine("\n=== All Examples Running ===");
Console.WriteLine("Starting server with all WebSocket endpoints...\n");
var server = WireMockServer.Start(new WireMockServerSettings
{
Port = 9091,
Logger = new WireMockConsoleLogger(),
WebSocketSettings = new WebSocketSettings
{
MaxConnections = 200
}
});
SetupAllEndpoints(server);
Console.WriteLine("All WebSocket endpoints are running:");
Console.WriteLine($" Echo: {server.Urls[0]}/ws/echo");
Console.WriteLine($" Chat: {server.Urls[0]}/ws/chat");
Console.WriteLine($" Broadcast: {server.Urls[0]}/ws/broadcast");
Console.WriteLine($" Game: {server.Urls[0]}/ws/game");
Console.WriteLine($" Time: {server.Urls[0]}/ws/time");
Console.WriteLine($" JSON: {server.Urls[0]}/ws/json");
Console.WriteLine("\nServer statistics:");
Console.WriteLine($" Total mappings: {server.Mappings.Count}");
Console.WriteLine("\nPress any key to view connection stats or CTRL+C to exit...");
while (true)
{
Console.ReadKey(true);
var connections = server.GetWebSocketConnections();
Console.WriteLine($"\nActive WebSocket connections: {connections.Count}");
foreach (var conn in connections)
{
Console.WriteLine($" - {conn.ConnectionId}: {conn.RequestMessage.Path} (State: {conn.WebSocket.State})");
}
Console.WriteLine("\nPress any key to refresh or CTRL+C to exit...");
}
}
private static void SetupAllEndpoints(WireMockServer server)
{
// Echo endpoint
server
.Given(Request.Create()
.WithPath("/ws/echo")
.WithWebSocketUpgrade()
)
.RespondWith(Response.Create()
.WithWebSocket(ws => ws.WithEcho())
);
// Chat endpoint
server
.Given(Request.Create()
.WithPath("/ws/chat")
.WithWebSocketUpgrade()
)
.RespondWith(Response.Create()
.WithWebSocket(ws => ws
.WithMessageHandler(async (message, context) =>
{
if (message.MessageType == WebSocketMessageType.Text)
{
await context.SendAsync($"Echo: {message.Text}");
}
})
)
);
// Broadcast endpoint
var broadcastGuid = Guid.NewGuid();
server
.Given(Request.Create()
.WithPath("/ws/broadcast")
.WithWebSocketUpgrade()
)
.WithGuid(broadcastGuid)
.RespondWith(Response.Create()
.WithWebSocket(ws => ws
.WithBroadcast()
.WithMessageHandler(async (message, context) =>
{
if (message.MessageType == WebSocketMessageType.Text)
{
await context.BroadcastTextAsync($"[Broadcast] {message.Text}");
}
})
)
);
// Game scenario endpoint
SetupGameScenario(server);
// Time endpoint
server
.Given(Request.Create()
.WithPath("/ws/time")
.WithWebSocketUpgrade()
)
.RespondWith(Response.Create()
.WithWebSocket(ws => ws
.WithMessageHandler(async (msg, ctx) =>
{
await ctx.SendAsync($"Server time: {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC");
})
)
);
// JSON endpoint
server
.Given(Request.Create()
.WithPath("/ws/json")
.WithWebSocketUpgrade()
)
.RespondWith(Response.Create()
.WithWebSocket(ws => ws
.WithMessageHandler(async (msg, ctx) =>
{
var response = new
{
timestamp = DateTime.UtcNow,
message = msg.Text,
connectionId = ctx.ConnectionId
};
await ctx.SendAsJsonAsync(response);
})
)
);
}
private static void SetupGameScenario(WireMockServer server)
{
server
.Given(Request.Create()
.WithPath("/ws/game")
.WithWebSocketUpgrade()
)
.InScenario("GameSession")
.WillSetStateTo("Lobby")
.RespondWith(Response.Create()
.WithWebSocket(ws => ws
.WithMessageHandler(async (msg, ctx) =>
{
await ctx.SendAsync("Welcome! Type 'ready' to start.");
})
)
);
server
.Given(Request.Create()
.WithPath("/ws/game")
.WithWebSocketUpgrade()
)
.InScenario("GameSession")
.WhenStateIs("Lobby")
.RespondWith(Response.Create()
.WithWebSocket(ws => ws
.WithMessageHandler(async (msg, ctx) =>
{
if (msg.Text?.ToLower() == "ready")
{
ctx.SetScenarioState("Playing");
await ctx.SendAsync("Game started!");
}
})
)
);
}
// Helper methods for testing
private static async Task TestWebSocketEcho(string baseUrl)
{
try
{
using var client = new ClientWebSocket();
var uri = new Uri($"{baseUrl.Replace("http://", "ws://")}/ws/echo");
Console.WriteLine($"\nConnecting to {uri}...");
await client.ConnectAsync(uri, CancellationToken.None);
Console.WriteLine("Connected!");
var testMessages = new[] { "Hello", "World", "WebSocket", "Test" };
foreach (var testMessage in testMessages)
{
Console.WriteLine($"\nSending: {testMessage}");
var bytes = Encoding.UTF8.GetBytes(testMessage);
await client.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, CancellationToken.None);
var buffer = new byte[1024];
var result = await client.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
var received = Encoding.UTF8.GetString(buffer, 0, result.Count);
Console.WriteLine($"Received: {received}");
}
await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "Test complete", CancellationToken.None);
Console.WriteLine("\nTest completed successfully!");
}
catch (Exception ex)
{
Console.WriteLine($"\nTest failed: {ex.Message}");
}
}
private static async Task TestWebSocketChat(string baseUrl)
{
try
{
using var client = new ClientWebSocket();
var uri = new Uri($"{baseUrl.Replace("http://", "ws://")}/ws/chat");
Console.WriteLine($"\nConnecting to {uri}...");
await client.ConnectAsync(uri, CancellationToken.None);
Console.WriteLine("Connected!");
var commands = new[] { "/help", "/time", "/echo Hello", "/upper test", "/reverse hello" };
foreach (var command in commands)
{
Console.WriteLine($"\nSending: {command}");
var bytes = Encoding.UTF8.GetBytes(command);
await client.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, CancellationToken.None);
var buffer = new byte[1024];
var result = await client.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
var received = Encoding.UTF8.GetString(buffer, 0, result.Count);
Console.WriteLine($"Received: {received}");
await Task.Delay(500);
}
await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "Test complete", CancellationToken.None);
Console.WriteLine("\nTest completed successfully!");
}
catch (Exception ex)
{
Console.WriteLine($"\nTest failed: {ex.Message}");
}
}
}

View File

@@ -0,0 +1,156 @@
# WireMock.Net WebSocket Examples
This project demonstrates all the WebSocket capabilities of WireMock.Net.
## Prerequisites
- .NET 8.0 SDK
- Optional: `wscat` for manual testing (`npm install -g wscat`)
## Running the Examples
```bash
cd examples/WireMock.Net.WebSocketExamples
dotnet run
```
## Available Examples
### 1. Echo Server
Simple WebSocket echo server that returns all messages back to the client.
**Test with:**
```bash
wscat -c ws://localhost:9091/ws/echo
```
### 2. Custom Message Handler
Chat server with commands: `/help`, `/time`, `/echo`, `/upper`, `/reverse`, `/quit`
**Test with:**
```bash
wscat -c ws://localhost:9091/ws/chat
> /help
> /time
> /echo Hello World
> /upper test
> /reverse hello
```
### 3. Broadcast Server
Messages sent by any client are broadcast to all connected clients.
**Test with multiple terminals:**
```bash
# Terminal 1
wscat -c ws://localhost:9091/ws/broadcast
# Terminal 2
wscat -c ws://localhost:9091/ws/broadcast
# Terminal 3
wscat -c ws://localhost:9091/ws/broadcast
```
Type messages in any terminal and see them appear in all terminals.
### 4. Scenario/State Machine
Game server with state transitions: Lobby -> Playing -> GameOver
**Test with:**
```bash
wscat -c ws://localhost:9091/ws/game
> ready
> attack
> defend
> quit
```
### 5. WebSocket Proxy
Proxies WebSocket connections to echo.websocket.org
**Test with:**
```bash
wscat -c ws://localhost:9091/ws/proxy
```
### 6. Multiple Endpoints
Runs multiple WebSocket endpoints simultaneously:
- `/ws/echo` - Echo server
- `/ws/time` - Returns server time
- `/ws/json` - Returns JSON responses
- `/ws/protocol` - Protocol-specific endpoint
### 7. All Examples
Runs all endpoints at once with connection statistics.
## Features Demonstrated
-**Echo Server** - Simple message echo
-**Custom Handlers** - Complex message processing
-**Broadcast** - Multi-client communication
-**Scenarios** - State machine patterns
-**Proxy** - Forwarding to real WebSocket servers
-**Protocol Negotiation** - Sec-WebSocket-Protocol support
-**JSON Messaging** - Structured data exchange
-**Connection Management** - Track and manage connections
-**Configuration** - Custom WebSocket settings
## Testing with wscat
Install wscat globally:
```bash
npm install -g wscat
```
Basic usage:
```bash
# Connect to endpoint
wscat -c ws://localhost:9091/ws/echo
# Connect with protocol
wscat -c ws://localhost:9091/ws/protocol -s chat
# Connect with headers
wscat -c ws://localhost:9091/ws/echo -H "X-Custom-Header: value"
```
## Testing with C# Client
The examples include built-in C# WebSocket clients for automated testing.
Select options 1 or 2 and press any key to run the automated tests.
## Configuration
WebSocket settings can be configured:
```csharp
var server = WireMockServer.Start(new WireMockServerSettings
{
Port = 9091,
WebSocketSettings = new WebSocketSettings
{
MaxConnections = 100,
ReceiveBufferSize = 8192,
MaxMessageSize = 1048576,
KeepAliveInterval = TimeSpan.FromSeconds(30),
CloseTimeout = TimeSpan.FromMinutes(10),
EnableCompression = true
}
});
```
## Monitoring
When running "All Examples" (option 7), press any key to view:
- Active connection count
- Connection IDs
- Request paths
- WebSocket states
## Notes
- All examples run on port 9091 by default
- Press CTRL+C to stop the server
- Multiple clients can connect simultaneously
- Connection states are tracked and can be queried

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>WireMock.Net.WebSocketExamples</AssemblyName>
<RootNamespace>WireMock.Net.WebSocketExamples</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
</ItemGroup>
</Project>

View File

@@ -1,25 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
<UserSecretsId>bb7d8355-68c4-4f81-8c2d-6cdd80cd7602</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Storage.Blobs" Version="12.14.1" />
<PackageReference Include="Azure.Storage.Files.Shares" Version="12.12.1" />
<PackageReference Include="Azure.Storage.Queues" Version="12.12.0" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="4.0.5" />
<PackageReference Include="Microsoft.Extensions.Azure" Version="1.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="6.0.1" />
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.1.3" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="local.settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
<UserSecretsId>bb7d8355-68c4-4f81-8c2d-6cdd80cd7602</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Storage.Blobs" Version="12.14.1" />
<PackageReference Include="Azure.Storage.Files.Shares" Version="12.12.1" />
<PackageReference Include="Azure.Storage.Queues" Version="12.12.0" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="4.0.5" />
<PackageReference Include="Microsoft.Extensions.Azure" Version="1.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="6.0.1" />
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.1.3" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="local.settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Text.RegularExpressions;
using JetBrains.Annotations;
using WireMock.Handlers;
using WireMock.Types;
@@ -123,7 +122,6 @@ public class SettingsModel
/// </summary>
public Dictionary<string, string[]>? ProtoDefinitions { get; set; }
#if NETSTANDARD1_3_OR_GREATER || NET461
/// <summary>
/// Server client certificate mode
/// </summary>
@@ -133,5 +131,9 @@ public class SettingsModel
/// Whether to accept any client certificate
/// </summary>
public bool AcceptAnyClientCertificate { get; set; }
#endif
/// <summary>
/// Gets or sets the WebSocket settings.
/// </summary>
public WebSocketSettingsModel? WebSocketSettings { get; set; }
}

View File

@@ -0,0 +1,40 @@
// Copyright © WireMock.Net
namespace WireMock.Admin.Settings;
/// <summary>
/// WebSocket Settings Model
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class WebSocketSettingsModel
{
/// <summary>
/// Maximum number of concurrent WebSocket connections (default: 100)
/// </summary>
public int MaxConnections { get; set; } = 100;
/// <summary>
/// Default receive buffer size in bytes (default: 4096)
/// </summary>
public int ReceiveBufferSize { get; set; } = 4096;
/// <summary>
/// Default keep-alive interval in seconds (default: 30)
/// </summary>
public int KeepAliveIntervalSeconds { get; set; } = 30;
/// <summary>
/// Maximum message size in bytes (default: 1048576 - 1 MB)
/// </summary>
public int MaxMessageSize { get; set; } = 1048576;
/// <summary>
/// Enable WebSocket compression (default: true)
/// </summary>
public bool EnableCompression { get; set; } = true;
/// <summary>
/// Default close timeout in minutes (default: 10)
/// </summary>
public int CloseTimeoutMinutes { get; set; } = 10;
}

View File

@@ -0,0 +1,39 @@
// Copyright © WireMock.Net
namespace WireMock.Constants;
/// <summary>
/// WebSocket constants
/// </summary>
public static class WebSocketConstants
{
/// <summary>
/// Default receive buffer size for WebSocket messages (4 KB)
/// </summary>
public const int DefaultReceiveBufferSize = 4096;
/// <summary>
/// Default keep-alive interval in seconds
/// </summary>
public const int DefaultKeepAliveIntervalSeconds = 30;
/// <summary>
/// Default close timeout in minutes
/// </summary>
public const int DefaultCloseTimeoutMinutes = 10;
/// <summary>
/// Minimum buffer size for WebSocket operations (1 KB)
/// </summary>
public const int MinimumBufferSize = 1024;
/// <summary>
/// Default maximum message size (1 MB)
/// </summary>
public const int DefaultMaxMessageSize = 1024 * 1024;
/// <summary>
/// Proxy forward buffer size (4 KB)
/// </summary>
public const int ProxyForwardBufferSize = 4096;
}

View File

@@ -2,9 +2,7 @@
using System;
using System.Collections.Generic;
#if NETSTANDARD1_3_OR_GREATER || NET461
using System.Security.Cryptography.X509Certificates;
#endif
using WireMock.Types;
using WireMock.Util;
@@ -118,13 +116,11 @@ public interface IRequestMessage
/// </summary>
byte[]? BodyAsBytes { get; }
#if MIMEKIT
/// <summary>
/// The original body as MimeMessage.
/// Convenience getter for Handlebars and WireMockAssertions.
/// </summary>
Models.Mime.IMimeMessageData? BodyAsMimeMessage { get; }
#endif
/// <summary>
/// The detected body type. Convenience getter for Handlebars.
@@ -169,10 +165,8 @@ public interface IRequestMessage
/// <returns>The query parameter value as WireMockList or null when not found.</returns>
WireMockList<string>? GetParameter(string key, bool ignoreCase = false);
#if NETSTANDARD1_3_OR_GREATER || NET461
/// <summary>
/// Gets the connection's client certificate
/// </summary>
X509Certificate2? ClientCertificate { get; }
#endif
}

View File

@@ -2,7 +2,6 @@
namespace WireMock.Types;
#if NETSTANDARD1_3_OR_GREATER || NET461
/// <summary>
/// Describes the client certificate requirements for a HTTPS connection.
/// This enum is the same as https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.server.kestrel.https.clientcertificatemode
@@ -29,5 +28,4 @@ public enum ClientCertificateMode
/// It may be requested by the application later.
/// </summary>
DelayCertificate,
}
#endif
}

View File

@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System;
namespace WireMock.Types;
[Flags]
@@ -13,5 +11,11 @@ public enum HostingScheme
Https = 0x2,
HttpAndHttps = Http | Https
HttpAndHttps = Http | Https,
Ws = 0x4,
Wss = 0x8,
WsAndWss = Ws | Wss
}

View File

@@ -4,7 +4,8 @@
<Description>Commonly used interfaces, models, enumerations and types.</Description>
<AssemblyTitle>WireMock.Net.Abstractions</AssemblyTitle>
<Authors>Stef Heyenrath</Authors>
<TargetFrameworks>net45;net451;net461;netstandard1.3;netstandard2.0;netstandard2.1</TargetFrameworks>
<!--<TargetFrameworks>net45;net451;net461;netstandard1.3;netstandard2.0;netstandard2.1</TargetFrameworks>-->
<TargetFrameworks>net48;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591;8603</NoWarn>
<AssemblyName>WireMock.Net.Abstractions</AssemblyName>
@@ -33,32 +34,22 @@
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netstandard2.1'">
<DefineConstants>$(DefineConstants);GRAPHQL;MIMEKIT;PROTOBUF</DefineConstants>
</PropertyGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net48' ">
<!-- CVE-2018-8292 / https://github.com/advisories/GHSA-7jgj-8wvc-jh57 -->
<PackageReference Include="System.Net.Http" Version="4.3.4" />
</ItemGroup>
<ItemGroup>
<!-- CVE-2018-8292 / https://github.com/advisories/GHSA-7jgj-8wvc-jh57 -->
<PackageReference Include="System.Net.Http " Version="4.3.4" />
<!-- See also https://mstack.nl/blog/20210801-source-generators -->
<PackageReference Include="FluentBuilder" Version="0.10.0">
<PackageReference Include="FluentBuilder" Version="0.13.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="PolySharp" Version="1.15.0">
<!-- Keep at 6.14.0 -->
<PackageReference Include="Polyfill" Version="6.14.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith('netstandard')) and '$(TargetFramework)' != 'netstandard1.0'">
<PackageReference Include="System.Security.Cryptography.X509Certificates" Version="4.3.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard1.3' or '$(TargetFramework)' == 'net45' or '$(TargetFramework)' == 'net451' or '$(TargetFramework)' == 'net461'">
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>
</Project>

View File

@@ -38,7 +38,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,10 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>AwesomeAssertions extensions for WireMock.Net</Description>
<AssemblyTitle>WireMock.Net.AwesomeAssertions</AssemblyTitle>
<Authors>Francesco Venturoli;Mahmoud Ali;Stef Heyenrath</Authors>
<TargetFrameworks>net47;netstandard2.0;netstandard2.1</TargetFrameworks>
<!--<TargetFrameworks>net47;netstandard2.0;netstandard2.1</TargetFrameworks>-->
<TargetFrameworks>net48;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyName>WireMock.Net.AwesomeAssertions</AssemblyName>
<PackageId>WireMock.Net.AwesomeAssertions</PackageId>
@@ -32,7 +33,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AwesomeAssertions" Version="9.0.0" />
<PackageReference Include="AwesomeAssertions" Version="9.1.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -21,4 +21,4 @@ public sealed class WireMockRequestInfo<TBody> : WireMockRequestInfo
/// Gets or initializes the deserialized request body.
/// </summary>
public TBody? Body { get; init; }
}
}

View File

@@ -25,10 +25,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.7.0" />
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.7.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WireMock.Net.Minimal\WireMock.Net.Minimal.csproj" />
<ProjectReference Include="..\WireMock.Net.Minimal\WireMock.Net.Minimal.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,10 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>FluentAssertions extensions for WireMock.Net</Description>
<AssemblyTitle>WireMock.Net.FluentAssertions</AssemblyTitle>
<Authors>Mahmoud Ali;Stef Heyenrath</Authors>
<TargetFrameworks>net451;net47;netstandard1.3;netstandard2.0;netstandard2.1</TargetFrameworks>
<!--<TargetFrameworks>net451;net47;netstandard1.3;netstandard2.0;netstandard2.1</TargetFrameworks>-->
<TargetFrameworks>net48;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyName>WireMock.Net.FluentAssertions</AssemblyName>
<PackageId>WireMock.Net.FluentAssertions</PackageId>
@@ -31,12 +32,8 @@
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net451' or '$(TargetFramework)' == 'netstandard1.3'">
<PackageReference Include="FluentAssertions" Version="5.10.3" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'net451' and '$(TargetFramework)' != 'netstandard1.3'">
<PackageReference Include="FluentAssertions" Version="6.5.1" />
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="7.2.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -4,7 +4,8 @@
<Description>GraphQL support for WireMock.Net</Description>
<AssemblyTitle>WireMock.Net.Matchers.GraphQL</AssemblyTitle>
<Authors>Stef Heyenrath</Authors>
<TargetFrameworks>netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net8.0</TargetFrameworks>
<!--<TargetFrameworks>netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net8.0</TargetFrameworks>-->
<TargetFrameworks>net48;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>wiremock;matchers;matcher;graphql</PackageTags>
<RootNamespace>WireMock</RootNamespace>
@@ -26,22 +27,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GraphQL.NewtonsoftJson" Version="8.2.1" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="System.Reflection.Emit" Version="4.3.0" />
<PackageReference Include="GraphQL.NewtonsoftJson" Version="8.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WireMock.Net.Shared\WireMock.Net.Shared.csproj" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="Nullable" Version="1.3.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@@ -108,72 +108,72 @@ public class CSharpCodeMatcher : ICSharpCodeMatcher
object? result;
#if (NET451 || NET452)
var compilerParams = new System.CodeDom.Compiler.CompilerParameters
{
GenerateInMemory = true,
GenerateExecutable = false,
ReferencedAssemblies =
{
"System.dll",
"System.Core.dll",
"Microsoft.CSharp.dll",
"Newtonsoft.Json.dll"
}
};
//#if (NET451 || NET452)
// var compilerParams = new System.CodeDom.Compiler.CompilerParameters
// {
// GenerateInMemory = true,
// GenerateExecutable = false,
// ReferencedAssemblies =
// {
// "System.dll",
// "System.Core.dll",
// "Microsoft.CSharp.dll",
// "Newtonsoft.Json.dll"
// }
// };
using (var codeProvider = new Microsoft.CSharp.CSharpCodeProvider())
{
var compilerResults = codeProvider.CompileAssemblyFromSource(compilerParams, source);
// using (var codeProvider = new Microsoft.CSharp.CSharpCodeProvider())
// {
// var compilerResults = codeProvider.CompileAssemblyFromSource(compilerParams, source);
if (compilerResults.Errors.Count != 0)
{
var errors = from System.CodeDom.Compiler.CompilerError er in compilerResults.Errors select er.ToString();
throw new WireMockException(string.Join(", ", errors));
}
// if (compilerResults.Errors.Count != 0)
// {
// var errors = from System.CodeDom.Compiler.CompilerError er in compilerResults.Errors select er.ToString();
// throw new WireMockException(string.Join(", ", errors));
// }
var helper = compilerResults.CompiledAssembly?.CreateInstance("CodeHelper");
if (helper == null)
{
throw new WireMockException("CSharpCodeMatcher: Unable to create instance from WireMock.CodeHelper");
}
// var helper = compilerResults.CompiledAssembly?.CreateInstance("CodeHelper");
// if (helper == null)
// {
// throw new WireMockException("CSharpCodeMatcher: Unable to create instance from WireMock.CodeHelper");
// }
var methodInfo = helper.GetType().GetMethod("IsMatch");
if (methodInfo == null)
{
throw new WireMockException("CSharpCodeMatcher: Unable to find method 'IsMatch' in WireMock.CodeHelper");
}
// var methodInfo = helper.GetType().GetMethod("IsMatch");
// if (methodInfo == null)
// {
// throw new WireMockException("CSharpCodeMatcher: Unable to find method 'IsMatch' in WireMock.CodeHelper");
// }
try
{
result = methodInfo.Invoke(helper, new[] { inputValue });
}
catch (Exception ex)
{
throw new WireMockException("CSharpCodeMatcher: Unable to call method 'IsMatch' in WireMock.CodeHelper", ex);
}
}
#elif (NET46 || NET461)
dynamic script;
try
{
script = CSScriptLibrary.CSScript.Evaluator.CompileCode(source).CreateObject("*");
}
catch (Exception ex)
{
throw new WireMockException("CSharpCodeMatcher: Unable to create compiler for WireMock.CodeHelper", ex);
}
try
{
result = script.IsMatch(inputValue);
}
catch (Exception ex)
{
throw new WireMockException("CSharpCodeMatcher: Problem calling method 'IsMatch' in WireMock.CodeHelper", ex);
}
// try
// {
// result = methodInfo.Invoke(helper, new[] { inputValue });
// }
// catch (Exception ex)
// {
// throw new WireMockException("CSharpCodeMatcher: Unable to call method 'IsMatch' in WireMock.CodeHelper", ex);
// }
// }
//#elif (NET46 || NET461)
// dynamic script;
// try
// {
// script = CSScriptLibrary.CSScript.Evaluator.CompileCode(source).CreateObject("*");
// }
// catch (Exception ex)
// {
// throw new WireMockException("CSharpCodeMatcher: Unable to create compiler for WireMock.CodeHelper", ex);
// }
#elif (NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP3_1 || NET5_0_OR_GREATER)
// try
// {
// result = script.IsMatch(inputValue);
// }
// catch (Exception ex)
// {
// throw new WireMockException("CSharpCodeMatcher: Problem calling method 'IsMatch' in WireMock.CodeHelper", ex);
// }
//#elif (NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP3_1 || NET5_0_OR_GREATER || NET48)
Assembly assembly;
try
{
@@ -202,9 +202,9 @@ public class CSharpCodeMatcher : ICSharpCodeMatcher
{
throw new WireMockException("CSharpCodeMatcher: Problem calling method 'IsMatch' in WireMock.CodeHelper", ex);
}
#else
throw new NotSupportedException("The 'CSharpCodeMatcher' cannot be used in netstandard 1.3");
#endif
//#else
// throw new NotSupportedException("The 'CSharpCodeMatcher' cannot be used in netstandard 1.3");
//#endif
try
{
return (bool)result;

View File

@@ -4,7 +4,8 @@
<Description>A CSharpCodeMatcher which can be used to match WireMock.Net Requests using C# code.</Description>
<AssemblyTitle>WireMock.Net.Matchers.CSharpCode</AssemblyTitle>
<Authors>Stef Heyenrath</Authors>
<TargetFrameworks>net451;net452;net46;net461;netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
<!--<TargetFrameworks>net451;net452;net46;net461;netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>-->
<TargetFrameworks>net48;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>wiremock;matchers;matcher;csharp;csharpcode</PackageTags>
<RootNamespace>WireMock</RootNamespace>
@@ -37,16 +38,8 @@
<ProjectReference Include="..\WireMock.Net.Minimal\WireMock.Net.Minimal.csproj" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net451' or '$(TargetFramework)' == 'net452' ">
<PackageReference Include="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" Version="3.6.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net46' or '$(TargetFramework)' == 'net461' ">
<PackageReference Include="CS-Script" Version="3.30.3" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netstandard2.1' or '$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' or '$(TargetFramework)' == 'net8.0'">
<PackageReference Include="CS-Script" Version="4.8.17" />
<ItemGroup>
<PackageReference Include="CS-Script" Version="4.11.0" />
</ItemGroup>
</Project>

View File

@@ -135,6 +135,6 @@ internal class MimeMessageDataWrapper : IMimeMessageData
/// <inheritdoc/>
public override string ToString()
{
return _message.ToString();
return _message.ToString() ?? string.Empty;
}
}

View File

@@ -4,7 +4,8 @@
<Description>MultiPart Mime support for WireMock.Net using MimeKitLite</Description>
<AssemblyTitle>WireMock.Net.MimePart</AssemblyTitle>
<Authors>Stef Heyenrath</Authors>
<TargetFrameworks>netstandard2.0;netstandard2.1;net462;net47;net48;net6.0;net8.0</TargetFrameworks>
<!--<TargetFrameworks>netstandard2.0;netstandard2.1;net462;net47;net48;net6.0;net8.0</TargetFrameworks>-->
<TargetFrameworks>net48;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>wiremock;matchers;matcher;mime;multipart;mimekit</PackageTags>
<RootNamespace>WireMock</RootNamespace>
@@ -36,24 +37,16 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Nullable" Version="1.3.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Stef.Validation" Version="0.1.1" />
<PackageReference Include="Stef.Validation" Version="0.2.0" />
</ItemGroup>
<!--<ItemGroup>
<PackageReference Include="MimeKitLite" Version="4.12.0" />
</ItemGroup>-->
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<PackageReference Include="MimeKitLite" Version="4.12.0" />
<PackageReference Include="MimeKitLite" Version="4.13.0" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Release'">
<PackageReference Include="ILRepack.Lib.MSBuild.Task" Version="2.0.40" PrivateAssets="All" />
<PackageReference Include="MimeKitLite" Version="4.12.0" PrivateAssets="All" />
<PackageReference Include="ILRepack.Lib.MSBuild.Task" Version="2.0.43" PrivateAssets="All" />
<PackageReference Include="MimeKitLite" Version="4.13.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>

View File

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

View File

@@ -1,6 +1,6 @@
// Copyright © WireMock.Net
#if NET451 || NET452 || NET46 || NET451 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0
#if NET48
using System.Text.RegularExpressions;
using WireMock.Constants;

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

@@ -11,29 +11,18 @@ 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,
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 +56,8 @@ internal static class HttpClientBuilder
}
}
#if NET5_0_OR_GREATER
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
ServicePointManager.ServerCertificateValidationCallback = (message, cert, chain, errors) => true;
#elif !NETSTANDARD1_3
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
ServicePointManager.ServerCertificateValidationCallback = (message, cert, chain, errors) => true;
#endif
return HttpClientFactory2.Create(handler);
}

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

@@ -43,11 +43,8 @@ internal static class CertificateLoader
}
finally
{
#if NETSTANDARD || NET46
certStore.Dispose();
#else
certStore.Close();
#endif
}
}
@@ -56,7 +53,7 @@ internal static class CertificateLoader
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 +63,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 +110,8 @@ internal static class CertificateLoader
}
finally
{
#if NETSTANDARD || NET46
certStore.Dispose();
#else
certStore.Close();
#endif
}
}
}

View File

@@ -2,6 +2,7 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Stef.Validation;
using WireMock.Matchers.Request;
using WireMock.Models;
@@ -145,9 +146,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

@@ -11,9 +11,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 +133,7 @@ public class XPathMatcher : IStringMatcher
}
catch
{
_xmlDocument = default;
_xmlDocument = null;
}
}
@@ -151,25 +149,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,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,9 +1,9 @@
// Copyright © WireMock.Net
#if ACTIVITY_TRACING_SUPPORTED
using System;
using System.Diagnostics;
using WireMock.Logging;
using WireMock.Settings;
namespace WireMock.Owin.ActivityTracing;
@@ -196,5 +196,4 @@ public static class WireMockActivitySource
activity.SetTag("exception.message", exception.Message);
activity.SetTag("exception.stacktrace", exception.ToString());
}
}
#endif
}

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,10 @@
// 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 +36,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 +45,7 @@ internal partial class AspNetCoreSelfHost
if (urlDetail.IsHttp2)
{
listenOptions.Protocols = HttpProtocols.Http2;
SetHttp2AsProtocolsOnListenOptions(listenOptions);
}
});
continue;
@@ -55,10 +53,7 @@ internal partial class AspNetCoreSelfHost
if (urlDetail.IsHttp2)
{
Listen(kestrelOptions, urlDetail, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http2;
});
Listen(kestrelOptions, urlDetail, SetHttp2AsProtocolsOnListenOptions);
continue;
}
@@ -66,6 +61,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 +115,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,12 +1,5 @@
// 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;
@@ -18,25 +11,22 @@ 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;
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)
{
@@ -74,7 +64,7 @@ internal partial class AspNetCoreSelfHost : IOwinSelfHost
services.AddSingleton<IOwinResponseMapper, OwinResponseMapper>();
services.AddSingleton<IGuidUtils, GuidUtils>();
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
#if NET8_0_OR_GREATER
AddCors(services);
#endif
_wireMockMiddlewareOptions.AdditionalServiceRegistration?.Invoke(services);
@@ -83,8 +73,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 +97,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");
#elif NET48
_logger.Info("Server using .NET Framework 4.8");
#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 +180,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

@@ -3,68 +3,44 @@
using System;
using System.Threading.Tasks;
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 Microsoft.AspNetCore.Http;
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)
{
_options = Guard.NotNull(options);
_responseMapper = Guard.NotNull(responseMapper);;
}
#else
public GlobalExceptionMiddleware(Next next, IWireMockMiddlewareOptions options, IOwinResponseMapper responseMapper)
{
Next = next;
_options = Guard.NotNull(options);
_responseMapper = Guard.NotNull(responseMapper);
}
#endif
public RequestDelegate? Next { get; }
#if USE_ASPNETCORE
public Next Next { get; }
#endif
public Task Invoke(HttpContext ctx)
{
return InvokeInternalAsync(ctx);
}
#if !USE_ASPNETCORE
public override Task Invoke(IContext ctx)
#else
public Task Invoke(IContext ctx)
#endif
private async Task InvokeInternalAsync(HttpContext ctx)
{
try
{
return InvokeInternalAsync(ctx);
}
private async Task InvokeInternalAsync(IContext ctx)
{
try
if (Next != null)
{
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);
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);
}
}
}
}

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

@@ -2,22 +2,17 @@
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 +36,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 +47,6 @@ internal interface IWireMockMiddlewareOptions
ClientCertificateMode ClientCertificateMode { get; set; }
bool AcceptAnyClientCertificate { get; set; }
#endif
IFileSystemHandler? FileSystemHandler { get; set; }
@@ -90,13 +83,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,13 @@
// 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;
//#if !USE_ASPNETCORE
//using IResponse = Microsoft.Owin.IOwinResponse;
//#else
//using IResponse = Microsoft.AspNetCore.Http.HttpResponse;
//#endif
namespace WireMock.Owin.Mappers;
@@ -18,6 +20,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,10 @@
// 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 +14,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 +66,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 +92,7 @@ internal class OwinRequestMapper : IOwinRequestMapper
{
clientIP = connection.RemoteIpAddress.ToString();
}
#endif
return (urlDetails, clientIP);
}
}

View File

@@ -8,22 +8,17 @@ 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 +32,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 +57,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 +123,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 +197,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 +213,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 +226,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 +234,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 +252,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,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,354 @@
// Copyright © WireMock.Net
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
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 WireMock.Constants;
using WireMock.Exceptions;
using WireMock.Http;
using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Owin.Mappers;
using WireMock.ResponseBuilders;
using WireMock.Serialization;
using WireMock.Settings;
using WireMock.Util;
#if ACTIVITY_TRACING_SUPPORTED
using System.Diagnostics;
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
namespace WireMock.Owin
namespace WireMock.Owin;
internal class WireMockMiddleware
{
internal class WireMockMiddleware : OwinMiddleware
private readonly object _lock = new();
private readonly IWireMockMiddlewareOptions _options;
private readonly IOwinRequestMapper _requestMapper;
private readonly IOwinResponseMapper _responseMapper;
private readonly IMappingMatcher _mappingMatcher;
private readonly LogEntryMapper _logEntryMapper;
private readonly IGuidUtils _guidUtils;
public WireMockMiddleware(
RequestDelegate next,
IWireMockMiddlewareOptions options,
IOwinRequestMapper requestMapper,
IOwinResponseMapper responseMapper,
IMappingMatcher mappingMatcher,
IGuidUtils guidUtils
)
{
private readonly object _lock = new();
private static readonly Task CompletedTask = Task.FromResult(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);
}
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)
public Task Invoke(HttpContext ctx)
{
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(WireMockMiddlewareOptions)] = _options;
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)
response = ResponseMessageBuilder.Create(500, ex.Message);
}
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
};
WireMockActivitySource.EnrichWithLogEntry(activity, log, _options.ActivityTracingOptions);
activity?.Dispose();
LogRequest(log, logRequest);
try
{
if (_options.SaveUnmatchedRequests == true && result.Match?.RequestMatchResult is not { IsPerfectMatch: true })
{
if (responseBuilder?.ProxyAndRecordSettings?.SaveMapping == true || targetMapping.Settings.ProxyAndRecordSettings?.SaveMapping == true)
{
_options.Mappings.TryAdd(theOptionalNewMapping.Guid, theOptionalNewMapping);
}
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);
}
var filename = $"{log.Guid}.LogEntry.json";
_options.FileSystemHandler?.WriteUnmatchedRequest(filename, JsonUtils.Serialize(log));
}
}
catch
{
// Empty catch
}
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;
}
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)
}
}
}

View File

@@ -2,20 +2,17 @@
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;
@@ -39,11 +36,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 +48,6 @@ internal class WireMockMiddlewareOptions : IWireMockMiddlewareOptions
/// <inheritdoc />
public bool AcceptAnyClientCertificate { get; set; }
#endif
/// <inheritdoc cref="IWireMockMiddlewareOptions.FileSystemHandler"/>
public IFileSystemHandler? FileSystemHandler { get; set; }
@@ -108,8 +103,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

@@ -19,6 +19,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 +36,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

@@ -0,0 +1,47 @@
// Copyright © WireMock.Net
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,11 +2,8 @@
// 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;
using Stef.Validation;
using WireMock.Matchers;
using WireMock.Matchers.Request;

View File

@@ -6,9 +6,10 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
#if USE_ASPNETCORE
using System.Security.Cryptography.X509Certificates;
#endif
//#if USE_ASPNETCORE
//using System.Security.Cryptography.X509Certificates;
//#endif
using Stef.Validation;
using WireMock.Models;
using WireMock.Owin;
@@ -82,11 +83,11 @@ public class RequestMessage : IRequestMessage
/// <inheritdoc />
public byte[]? BodyAsBytes { get; }
#if MIMEKIT
//#if MIMEKIT
/// <inheritdoc />
[Newtonsoft.Json.JsonIgnore] // Issue 1001
public Models.Mime.IMimeMessageData? BodyAsMimeMessage { get; }
#endif
//#endif
/// <inheritdoc />
public string? DetectedBodyType { get; }
@@ -109,10 +110,10 @@ public class RequestMessage : IRequestMessage
/// <inheritdoc />
public string Origin { get; }
#if USE_ASPNETCORE
//#if USE_ASPNETCORE
/// <inheritdoc />
public X509Certificate2? ClientCertificate { get; }
#endif
//#endif
/// <summary>
/// Used for Unit Testing
@@ -136,9 +137,9 @@ public class RequestMessage : IRequestMessage
IDictionary<string, string[]>? headers = null,
IDictionary<string, string>? cookies = null,
string httpVersion = "1.1"
#if USE_ASPNETCORE
//#if USE_ASPNETCORE
, X509Certificate2? clientCertificate = null
#endif
//#endif
)
{
Guard.NotNull(urlDetails);
@@ -178,16 +179,16 @@ public class RequestMessage : IRequestMessage
Query = QueryStringParser.Parse(RawQuery, options?.QueryParameterMultipleValueSupport);
QueryIgnoreCase = new Dictionary<string, WireMockList<string>>(Query, StringComparer.OrdinalIgnoreCase);
#if USE_ASPNETCORE
//#if USE_ASPNETCORE
ClientCertificate = clientCertificate;
#endif
//#endif
#if MIMEKIT
//#if MIMEKIT
if (TypeLoader.TryLoadStaticInstance<IMimeKitUtils>(out var mimeKitUtils) && mimeKitUtils.TryGetMimeMessage(this, out var mimeMessage))
{
BodyAsMimeMessage = mimeMessage;
}
#endif
//#endif
}
/// <inheritdoc />
@@ -199,7 +200,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

@@ -0,0 +1,49 @@
// Copyright © WireMock.Net
using System;
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();
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();
builder.WithProxy(settings);
WebSocketBuilder = builder;
return this;
}
}

View File

@@ -4,16 +4,16 @@
// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root.
using System;
using System.Net;
using System.Net.Http;
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 +187,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

@@ -8,6 +8,7 @@ using WireMock.ResponseBuilders;
using WireMock.Types;
using WireMock.Util;
using Stef.Validation;
using WireMock.WebSockets;
namespace WireMock;

View File

@@ -2,6 +2,7 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Stef.Validation;
using WireMock.Settings;
@@ -9,15 +10,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);
}
}

View File

@@ -2,6 +2,7 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Stef.Validation;
using WireMock.Settings;
@@ -9,16 +10,17 @@ namespace WireMock.ResponseProviders;
internal class DynamicResponseProvider : IResponseProvider
{
private readonly Func<IRequestMessage, IResponseMessage> _responseMessageFunc;
private readonly Func<HttpContext, IRequestMessage, IResponseMessage> _responseMessageFunc;
public DynamicResponseProvider(Func<IRequestMessage, IResponseMessage> responseMessageFunc)
public DynamicResponseProvider(Func<HttpContext, IRequestMessage, IResponseMessage> responseMessageFunc)
{
_responseMessageFunc = Guard.NotNull(responseMessageFunc);
}
public Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IMapping mapping, IRequestMessage requestMessage, WireMockServerSettings settings)
/// <inheritdoc />
public Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IMapping mapping, HttpContext context, IRequestMessage requestMessage, WireMockServerSettings settings)
{
(IResponseMessage responseMessage, IMapping? mapping) result = (_responseMessageFunc(requestMessage), null);
(IResponseMessage responseMessage, IMapping? mapping) result = (_responseMessageFunc(context, requestMessage), null);
return Task.FromResult(result);
}
}

View File

@@ -2,6 +2,7 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Stef.Validation;
using WireMock.Settings;
@@ -9,17 +10,18 @@ namespace WireMock.ResponseProviders;
internal class ProxyAsyncResponseProvider : IResponseProvider
{
private readonly Func<IRequestMessage, WireMockServerSettings, Task<IResponseMessage>> _responseMessageFunc;
private readonly Func<HttpContext, IRequestMessage, WireMockServerSettings, Task<IResponseMessage>> _responseMessageFunc;
private readonly WireMockServerSettings _settings;
public ProxyAsyncResponseProvider(Func<IRequestMessage, WireMockServerSettings, Task<IResponseMessage>> responseMessageFunc, WireMockServerSettings settings)
public ProxyAsyncResponseProvider(Func<HttpContext, IRequestMessage, WireMockServerSettings, Task<IResponseMessage>> responseMessageFunc, WireMockServerSettings settings)
{
_responseMessageFunc = Guard.NotNull(responseMessageFunc);
_settings = Guard.NotNull(settings);
}
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, _settings).ConfigureAwait(false), null);
return (await _responseMessageFunc(context, requestMessage, _settings).ConfigureAwait(false), null);
}
}

View File

@@ -0,0 +1,339 @@
// Copyright © WireMock.Net
using System.Net;
using System.Net.WebSockets;
using System.Text;
using Microsoft.AspNetCore.Http;
using Stef.Validation;
using WireMock.Constants;
using WireMock.Owin;
using WireMock.Settings;
using WireMock.WebSockets;
namespace WireMock.ResponseProviders;
internal class WebSocketResponseProvider : IResponseProvider
{
private readonly WebSocketBuilder _builder;
public WebSocketResponseProvider(WebSocketBuilder builder)
{
_builder = Guard.NotNull(builder);
}
public async Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(
IMapping mapping,
HttpContext context,
IRequestMessage requestMessage,
WireMockServerSettings settings)
{
// Check if this is a WebSocket upgrade request
if (!context.WebSockets.IsWebSocketRequest)
{
return (ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, "Bad Request: Not a WebSocket upgrade request"), null);
}
try
{
// Accept the WebSocket connection
#if NET8_0_OR_GREATER
var acceptContext = new WebSocketAcceptContext
{
SubProtocol = _builder.AcceptProtocol,
KeepAliveInterval = _builder.KeepAliveIntervalSeconds ?? TimeSpan.FromSeconds(WebSocketConstants.DefaultKeepAliveIntervalSeconds)
};
var webSocket = await context.WebSockets.AcceptWebSocketAsync(acceptContext).ConfigureAwait(false);
#else
var webSocket = await context.WebSockets.AcceptWebSocketAsync(_builder.AcceptProtocol).ConfigureAwait(false);
#endif
// Get options from HttpContext.Items (set by WireMockMiddleware)
if (!context.Items.TryGetValue(nameof(WireMockMiddlewareOptions), out var optionsObj) ||
optionsObj is not IWireMockMiddlewareOptions options)
{
throw new InvalidOperationException("WireMockMiddlewareOptions not found in HttpContext.Items");
}
// Get or create registry from options
var registry = _builder.IsBroadcast
? options.WebSocketRegistries.GetOrAdd(mapping.Guid, _ => new WebSocketConnectionRegistry())
: null;
// Create WebSocket context
var wsContext = new WireMockWebSocketContext(
context,
webSocket,
requestMessage,
mapping,
registry,
_builder
);
// Update scenario state following the same pattern as WireMockMiddleware
if (mapping.Scenario != null)
{
wsContext.UpdateScenarioState();
}
// Add to registry if broadcast is enabled
if (registry != null)
{
registry.AddConnection(wsContext);
}
try
{
// Handle the WebSocket based on configuration
if (_builder.ProxySettings != null)
{
await HandleProxyAsync(wsContext, _builder.ProxySettings).ConfigureAwait(false);
}
else if (_builder.IsEcho)
{
await HandleEchoAsync(wsContext).ConfigureAwait(false);
}
else if (_builder.MessageHandler != null)
{
await HandleCustomAsync(wsContext, _builder.MessageHandler).ConfigureAwait(false);
}
else if (_builder.MessageSequence != null)
{
await HandleSequenceAsync(wsContext, _builder.MessageSequence).ConfigureAwait(false);
}
else
{
// Default: keep connection open until client closes
await WaitForCloseAsync(wsContext).ConfigureAwait(false);
}
}
finally
{
// Remove from registry
registry?.RemoveConnection(wsContext.ConnectionId);
}
// Return special marker to indicate WebSocket was handled
return (new WebSocketHandledResponse(), null);
}
catch (Exception ex)
{
settings.Logger?.Error($"WebSocket error for mapping '{mapping.Guid}': {ex.Message}", ex);
// If we haven't upgraded yet, we can return HTTP error
if (!context.Response.HasStarted)
{
return (ResponseMessageBuilder.Create(HttpStatusCode.InternalServerError, $"WebSocket error: {ex.Message}"), null);
}
// Already upgraded - return marker
return (new WebSocketHandledResponse(), null);
}
}
private async Task HandleEchoAsync(WireMockWebSocketContext context)
{
var bufferSize = context.Builder.MaxMessageSize ?? WebSocketConstants.DefaultReceiveBufferSize;
var buffer = new byte[bufferSize];
var timeout = context.Builder.CloseTimeout ?? TimeSpan.FromMinutes(WebSocketConstants.DefaultCloseTimeoutMinutes);
var cts = new CancellationTokenSource(timeout);
try
{
while (context.WebSocket.State == WebSocketState.Open && !cts.Token.IsCancellationRequested)
{
var result = await context.WebSocket.ReceiveAsync(
new ArraySegment<byte>(buffer),
cts.Token
).ConfigureAwait(false);
if (result.MessageType == WebSocketMessageType.Close)
{
await context.CloseAsync(
WebSocketCloseStatus.NormalClosure,
"Closed by client"
).ConfigureAwait(false);
break;
}
// Echo back
await context.WebSocket.SendAsync(
new ArraySegment<byte>(buffer, 0, result.Count),
result.MessageType,
result.EndOfMessage,
cts.Token
).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
if (context.WebSocket.State == WebSocketState.Open)
{
await context.CloseAsync(WebSocketCloseStatus.NormalClosure, "Timeout");
}
}
}
private async Task HandleCustomAsync(
WireMockWebSocketContext context,
Func<WebSocketMessage, IWebSocketContext, Task> handler)
{
var bufferSize = context.Builder.MaxMessageSize ?? WebSocketConstants.DefaultReceiveBufferSize;
var buffer = new byte[bufferSize];
var timeout = context.Builder.CloseTimeout ?? TimeSpan.FromMinutes(WebSocketConstants.DefaultCloseTimeoutMinutes);
var cts = new CancellationTokenSource(timeout);
try
{
while (context.WebSocket.State == WebSocketState.Open && !cts.Token.IsCancellationRequested)
{
var result = await context.WebSocket.ReceiveAsync(
new ArraySegment<byte>(buffer),
cts.Token
).ConfigureAwait(false);
if (result.MessageType == WebSocketMessageType.Close)
{
await context.CloseAsync(
WebSocketCloseStatus.NormalClosure,
"Closed by client"
).ConfigureAwait(false);
break;
}
var message = CreateWebSocketMessage(result, buffer);
// Call custom handler
await handler(message, context).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
if (context.WebSocket.State == WebSocketState.Open)
{
await context.CloseAsync(WebSocketCloseStatus.NormalClosure, "Timeout");
}
}
}
private async Task HandleSequenceAsync(WireMockWebSocketContext context, WebSocketMessageSequence sequence)
{
await sequence.ExecuteAsync(context).ConfigureAwait(false);
}
private async Task HandleProxyAsync(WireMockWebSocketContext context, ProxyAndRecordSettings settings)
{
using var clientWebSocket = new ClientWebSocket();
var targetUri = new Uri(settings.Url);
await clientWebSocket.ConnectAsync(targetUri, CancellationToken.None).ConfigureAwait(false);
// Bidirectional proxy
var clientToServer = ForwardMessagesAsync(context.WebSocket, clientWebSocket);
var serverToClient = ForwardMessagesAsync(clientWebSocket, context.WebSocket);
await Task.WhenAny(clientToServer, serverToClient).ConfigureAwait(false);
// Close both
if (context.WebSocket.State == WebSocketState.Open)
{
await context.CloseAsync(WebSocketCloseStatus.NormalClosure, "Proxy closed");
}
if (clientWebSocket.State == WebSocketState.Open)
{
await clientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Proxy closed", CancellationToken.None);
}
}
private async Task ForwardMessagesAsync(WebSocket source, WebSocket destination)
{
var buffer = new byte[WebSocketConstants.ProxyForwardBufferSize];
while (source.State == WebSocketState.Open && destination.State == WebSocketState.Open)
{
var result = await source.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close)
{
await destination.CloseAsync(
result.CloseStatus ?? WebSocketCloseStatus.NormalClosure,
result.CloseStatusDescription,
CancellationToken.None
);
break;
}
await destination.SendAsync(
new ArraySegment<byte>(buffer, 0, result.Count),
result.MessageType,
result.EndOfMessage,
CancellationToken.None
);
}
}
private async Task WaitForCloseAsync(WireMockWebSocketContext context)
{
var buffer = new byte[WebSocketConstants.MinimumBufferSize];
var timeout = context.Builder.CloseTimeout ?? TimeSpan.FromMinutes(WebSocketConstants.DefaultCloseTimeoutMinutes);
var cts = new CancellationTokenSource(timeout);
try
{
while (context.WebSocket.State == WebSocketState.Open && !cts.Token.IsCancellationRequested)
{
var result = await context.WebSocket.ReceiveAsync(
new ArraySegment<byte>(buffer),
cts.Token
);
if (result.MessageType == WebSocketMessageType.Close)
{
await context.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closed by client");
break;
}
}
}
catch (OperationCanceledException)
{
if (context.WebSocket.State == WebSocketState.Open)
{
await context.CloseAsync(WebSocketCloseStatus.NormalClosure, "Timeout");
}
}
}
private static WebSocketMessage CreateWebSocketMessage(WebSocketReceiveResult result, byte[] buffer)
{
var message = new WebSocketMessage
{
MessageType = result.MessageType,
EndOfMessage = result.EndOfMessage,
Timestamp = DateTime.UtcNow
};
if (result.MessageType == WebSocketMessageType.Text)
{
message.Text = Encoding.UTF8.GetString(buffer, 0, result.Count);
}
else
{
message.Bytes = new byte[result.Count];
Array.Copy(buffer, message.Bytes, result.Count);
}
return message;
}
}
/// <summary>
/// Special response marker to indicate WebSocket has been handled
/// </summary>
internal class WebSocketHandledResponse : ResponseMessage
{
public WebSocketHandledResponse()
{
// 101 Switching Protocols
StatusCode = (int)HttpStatusCode.SwitchingProtocols;
}
}

View File

@@ -30,7 +30,7 @@ public class MappingFileNameSanitizer
if (!string.IsNullOrEmpty(mapping.Title))
{
// remove 'Proxy Mapping for ' and an extra space character after the HTTP request method
name = mapping.Title.Replace(ProxyAndRecordSettings.DefaultPrefixForSavedMappingFile, "").Replace(' '.ToString(), string.Empty);
name = mapping.Title!.Replace(ProxyAndRecordSettings.DefaultPrefixForSavedMappingFile, "").Replace(' '.ToString(), string.Empty);
if (_settings.ProxyAndRecordSettings?.AppendGuidToSavedMappingFile == true)
{
name += $"{ReplaceChar}{mapping.Guid}";

View File

@@ -218,7 +218,7 @@ internal class MatcherMapper
model.ContentTypeMatcher = Map(mimePartMatcher.ContentTypeMatcher);
break;
#if PROTOBUF
//#if PROTOBUF
case IProtoBufMatcher protoBufMatcher:
protoBufMatcher.ProtoDefinition().Value(id => model.Pattern = id, texts =>
{
@@ -235,7 +235,7 @@ internal class MatcherMapper
model.ProtoBufMessageType = protoBufMatcher.MessageType;
model.ContentMatcher = Map(protoBufMatcher.Matcher);
break;
#endif
//#endif
}
afterMap?.Invoke(model);

View File

@@ -37,7 +37,7 @@ internal class RespondWithAProvider : IRespondWithAProvider
private int _timesInSameState = 1;
private bool? _useWebhookFireAndForget;
private double? _probability;
private GraphQLSchemaDetails? _graphQLSchemaDetails;
private GraphQLSchemaDetails? _graphQLSchemaDetails; // Future Use.
public Guid Guid { get; private set; }
@@ -79,6 +79,12 @@ internal class RespondWithAProvider : IRespondWithAProvider
/// <inheritdoc />
public void RespondWith(IResponseProvider provider)
{
if (provider is Response response && response.WebSocketBuilder != null)
{
// If the provider is a Response with a WebSocketBuilder, we need to use a WebSocketResponseProvider instead.
provider = new WebSocketResponseProvider(response.WebSocketBuilder);
}
var mapping = new Mapping
(
Guid,

View File

@@ -7,6 +7,7 @@ using System.Linq;
using System.Net;
using System.Text;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Stef.Validation;
@@ -254,7 +255,7 @@ public partial class WireMockServer
#endregion
#region Health
private static IResponseMessage HealthGet(IRequestMessage requestMessage)
private static IResponseMessage HealthGet(HttpContext _, IRequestMessage requestMessage)
{
return new ResponseMessage
{
@@ -270,7 +271,7 @@ public partial class WireMockServer
#endregion
#region Settings
private IResponseMessage SettingsGet(IRequestMessage requestMessage)
private IResponseMessage SettingsGet(HttpContext _, IRequestMessage requestMessage)
{
var model = new SettingsModel
{
@@ -295,11 +296,9 @@ public partial class WireMockServer
WatchStaticMappings = _settings.WatchStaticMappings,
WatchStaticMappingsInSubdirectories = _settings.WatchStaticMappingsInSubdirectories,
#if USE_ASPNETCORE
AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate,
ClientCertificateMode = _settings.ClientCertificateMode,
CorsPolicyOptions = _settings.CorsPolicyOptions?.ToString()
#endif
};
model.ProxyAndRecordSettings = TinyMapperUtils.Instance.Map(_settings.ProxyAndRecordSettings);
@@ -307,7 +306,7 @@ public partial class WireMockServer
return ToJson(model);
}
private IResponseMessage SettingsUpdate(IRequestMessage requestMessage)
private IResponseMessage SettingsUpdate(HttpContext _, IRequestMessage requestMessage)
{
var settings = DeserializeObject<SettingsModel>(requestMessage);
@@ -333,12 +332,12 @@ public partial class WireMockServer
InitSettings(_settings);
#if USE_ASPNETCORE
//#if USE_ASPNETCORE
if (Enum.TryParse<CorsPolicyOptions>(settings.CorsPolicyOptions, true, out var corsPolicyOptions))
{
_settings.CorsPolicyOptions = corsPolicyOptions;
}
#endif
//#endif
WireMockMiddlewareOptionsHelper.InitFromSettings(_settings, _options, o =>
{
@@ -347,11 +346,11 @@ public partial class WireMockServer
o.RequestProcessingDelay = TimeSpan.FromMilliseconds(settings.GlobalProcessingDelay.Value);
}
#if USE_ASPNETCORE
//#if USE_ASPNETCORE
o.CorsPolicyOptions = corsPolicyOptions;
o.ClientCertificateMode = _settings.ClientCertificateMode;
o.ClientCertificateMode = (Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode) _settings.ClientCertificateMode;
o.AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate;
#endif
//#endif
});
return ResponseMessageBuilder.Create(200, "Settings updated");
@@ -359,9 +358,9 @@ public partial class WireMockServer
#endregion Settings
#region Mapping/{guid}
private IResponseMessage MappingGet(IRequestMessage requestMessage)
private IResponseMessage MappingGet(HttpContext context, IRequestMessage requestMessage)
{
var mapping = FindMappingByGuid(requestMessage);
var mapping = FindMappingByGuid(context, requestMessage);
if (mapping == null)
{
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
@@ -373,7 +372,7 @@ public partial class WireMockServer
return ToJson(model);
}
private IResponseMessage MappingCodeGet(IRequestMessage requestMessage)
private IResponseMessage MappingCodeGet(HttpContext context, IRequestMessage requestMessage)
{
if (TryParseGuidFromRequestMessage(requestMessage, out var guid))
{
@@ -403,12 +402,12 @@ public partial class WireMockServer
return defaultValue;
}
private IMapping? FindMappingByGuid(IRequestMessage requestMessage)
private IMapping? FindMappingByGuid(HttpContext _, IRequestMessage requestMessage)
{
return TryParseGuidFromRequestMessage(requestMessage, out var guid) ? Mappings.FirstOrDefault(m => !m.IsAdminInterface && m.Guid == guid) : null;
}
private IResponseMessage MappingPut(IRequestMessage requestMessage)
private IResponseMessage MappingPut(HttpContext _, IRequestMessage requestMessage)
{
if (TryParseGuidFromRequestMessage(requestMessage, out var guid))
{
@@ -422,7 +421,7 @@ public partial class WireMockServer
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found");
}
private IResponseMessage MappingDelete(IRequestMessage requestMessage)
private IResponseMessage MappingDelete(HttpContext _, IRequestMessage requestMessage)
{
if (TryParseGuidFromRequestMessage(requestMessage, out var guid) && DeleteMapping(guid))
{
@@ -441,7 +440,7 @@ public partial class WireMockServer
#endregion Mapping/{guid}
#region Mappings
private IResponseMessage SwaggerGet(IRequestMessage requestMessage)
private IResponseMessage SwaggerGet(HttpContext _, IRequestMessage requestMessage)
{
return new ResponseMessage
{
@@ -455,7 +454,7 @@ public partial class WireMockServer
};
}
private IResponseMessage MappingsSave(IRequestMessage requestMessage)
private IResponseMessage MappingsSave(HttpContext _, IRequestMessage requestMessage)
{
SaveStaticMappings();
@@ -467,12 +466,12 @@ public partial class WireMockServer
return _mappingBuilder.GetMappings();
}
private IResponseMessage MappingsGet(IRequestMessage requestMessage)
private IResponseMessage MappingsGet(HttpContext _, IRequestMessage requestMessage)
{
return ToJson(ToMappingModels());
}
private IResponseMessage MappingsCodeGet(IRequestMessage requestMessage)
private IResponseMessage MappingsCodeGet(HttpContext _, IRequestMessage requestMessage)
{
var converterType = GetEnumFromQuery(requestMessage, MappingConverterType.Server);
@@ -481,7 +480,7 @@ public partial class WireMockServer
return ToResponseMessage(code);
}
private IResponseMessage MappingsPost(IRequestMessage requestMessage)
private IResponseMessage MappingsPost(HttpContext _, IRequestMessage requestMessage)
{
try
{
@@ -508,7 +507,7 @@ public partial class WireMockServer
}
}
private IResponseMessage MappingsDelete(IRequestMessage requestMessage)
private IResponseMessage MappingsDelete(HttpContext _, IRequestMessage requestMessage)
{
if (!string.IsNullOrEmpty(requestMessage.Body))
{
@@ -562,7 +561,7 @@ public partial class WireMockServer
return deletedGuids;
}
private IResponseMessage MappingsReset(IRequestMessage requestMessage)
private IResponseMessage MappingsReset(HttpContext _, IRequestMessage requestMessage)
{
ResetMappings();
@@ -581,7 +580,7 @@ public partial class WireMockServer
return ResponseMessageBuilder.Create(200, message);
}
private IResponseMessage ReloadStaticMappings(IRequestMessage _)
private IResponseMessage ReloadStaticMappings(HttpContext _, IRequestMessage __)
{
ReadStaticMappings();
@@ -590,7 +589,7 @@ public partial class WireMockServer
#endregion Mappings
#region Request/{guid}
private IResponseMessage RequestGet(IRequestMessage requestMessage)
private IResponseMessage RequestGet(HttpContext _, IRequestMessage requestMessage)
{
if (TryParseGuidFromRequestMessage(requestMessage, out var guid))
{
@@ -606,7 +605,7 @@ public partial class WireMockServer
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Request not found");
}
private IResponseMessage RequestDelete(IRequestMessage requestMessage)
private IResponseMessage RequestDelete(HttpContext _, IRequestMessage requestMessage)
{
if (TryParseGuidFromRequestMessage(requestMessage, out var guid) && DeleteLogEntry(guid))
{
@@ -619,7 +618,7 @@ public partial class WireMockServer
#endregion Request/{guid}
#region Requests
private IResponseMessage RequestsGet(IRequestMessage requestMessage)
private IResponseMessage RequestsGet(HttpContext _, IRequestMessage requestMessage)
{
var logEntryMapper = new LogEntryMapper(_options);
var result = LogEntries
@@ -629,7 +628,7 @@ public partial class WireMockServer
return ToJson(result);
}
private IResponseMessage RequestsDelete(IRequestMessage requestMessage)
private IResponseMessage RequestsDelete(HttpContext _, IRequestMessage requestMessage)
{
ResetLogEntries();
@@ -638,7 +637,7 @@ public partial class WireMockServer
#endregion Requests
#region Requests/find
private IResponseMessage RequestsFind(IRequestMessage requestMessage)
private IResponseMessage RequestsFind(HttpContext _, IRequestMessage requestMessage)
{
var requestModel = DeserializeObject<RequestModel>(requestMessage);
@@ -660,7 +659,7 @@ public partial class WireMockServer
return ToJson(result);
}
private IResponseMessage RequestsFindByMappingGuid(IRequestMessage requestMessage)
private IResponseMessage RequestsFindByMappingGuid(HttpContext _, IRequestMessage requestMessage)
{
if (requestMessage.Query != null &&
requestMessage.Query.TryGetValue("mappingGuid", out var value) &&
@@ -678,7 +677,7 @@ public partial class WireMockServer
#endregion Requests/find
#region Scenarios
private IResponseMessage ScenariosGet(IRequestMessage requestMessage)
private IResponseMessage ScenariosGet(HttpContext _, IRequestMessage requestMessage)
{
var scenariosStates = Scenarios.Values.Select(s => new ScenarioStateModel
{
@@ -692,14 +691,14 @@ public partial class WireMockServer
return ToJson(scenariosStates, true);
}
private IResponseMessage ScenariosReset(IRequestMessage requestMessage)
private IResponseMessage ScenariosReset(HttpContext _, IRequestMessage requestMessage)
{
ResetScenarios();
return ResponseMessageBuilder.Create(200, "Scenarios reset");
}
private IResponseMessage ScenarioReset(IRequestMessage requestMessage)
private IResponseMessage ScenarioReset(HttpContext _, IRequestMessage requestMessage)
{
var name = string.Equals(HttpRequestMethod.DELETE, requestMessage.Method, StringComparison.OrdinalIgnoreCase) ?
requestMessage.Path.Substring(_adminPaths!.Scenarios.Length + 1) :
@@ -710,7 +709,7 @@ public partial class WireMockServer
ResponseMessageBuilder.Create(HttpStatusCode.NotFound, $"No scenario found by name '{name}'.");
}
private IResponseMessage ScenariosSetState(IRequestMessage requestMessage)
private IResponseMessage ScenariosSetState(HttpContext _, IRequestMessage requestMessage)
{
var name = requestMessage.Path.Split('/').Reverse().Skip(1).First();
if (!_options.Scenarios.ContainsKey(name))
@@ -900,6 +899,6 @@ public partial class WireMockServer
}
var singleResult = ((JObject)value).ToObject<T>();
return new[] { singleResult! };
return [singleResult!];
}
}

View File

@@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using Microsoft.AspNetCore.Http;
using WireMock.Types;
using WireMock.Util;
@@ -14,7 +15,7 @@ public partial class WireMockServer
private static readonly Encoding[] FileBodyIsString = [Encoding.UTF8, Encoding.ASCII];
#region ProtoDefinitions/{id}
private IResponseMessage ProtoDefinitionAdd(IRequestMessage requestMessage)
private IResponseMessage ProtoDefinitionAdd(HttpContext _, IRequestMessage requestMessage)
{
if (requestMessage.Body is null)
{
@@ -30,7 +31,7 @@ public partial class WireMockServer
#endregion
#region Files/{filename}
private IResponseMessage FilePost(IRequestMessage requestMessage)
private IResponseMessage FilePost(HttpContext _, IRequestMessage requestMessage)
{
if (requestMessage.BodyAsBytes is null)
{
@@ -50,7 +51,7 @@ public partial class WireMockServer
return ResponseMessageBuilder.Create(HttpStatusCode.OK, "File created");
}
private IResponseMessage FilePut(IRequestMessage requestMessage)
private IResponseMessage FilePut(HttpContext _, IRequestMessage requestMessage)
{
if (requestMessage.BodyAsBytes is null)
{
@@ -70,7 +71,7 @@ public partial class WireMockServer
return ResponseMessageBuilder.Create(HttpStatusCode.OK, "File updated");
}
private IResponseMessage FileGet(IRequestMessage requestMessage)
private IResponseMessage FileGet(HttpContext _, IRequestMessage requestMessage)
{
var filename = GetFileNameFromRequestMessage(requestMessage);
@@ -105,8 +106,7 @@ public partial class WireMockServer
/// Checks if file exists.
/// Note: Response is returned with no body as a head request doesn't accept a body, only the status code.
/// </summary>
/// <param name="requestMessage">The request message.</param>
private IResponseMessage FileHead(IRequestMessage requestMessage)
private IResponseMessage FileHead(HttpContext _, IRequestMessage requestMessage)
{
var filename = GetFileNameFromRequestMessage(requestMessage);
@@ -119,7 +119,7 @@ public partial class WireMockServer
return ResponseMessageBuilder.Create(HttpStatusCode.NoContent);
}
private IResponseMessage FileDelete(IRequestMessage requestMessage)
private IResponseMessage FileDelete(HttpContext _, IRequestMessage requestMessage)
{
var filename = GetFileNameFromRequestMessage(requestMessage);

View File

@@ -6,12 +6,13 @@ using System.IO;
using System.Linq;
using System.Text;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json.Linq;
using Stef.Validation;
using WireMock.Matchers;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Util;
using Stef.Validation;
using OrgMapping = WireMock.Org.Abstractions.Mapping;
namespace WireMock.Server;
@@ -46,7 +47,7 @@ public partial class WireMockServer
}
}
private IResponseMessage MappingsPostWireMockOrg(IRequestMessage requestMessage)
private IResponseMessage MappingsPostWireMockOrg(HttpContext _, IRequestMessage requestMessage)
{
try
{
@@ -76,7 +77,7 @@ public partial class WireMockServer
}
}
private Guid? ConvertWireMockOrgMappingAndRegisterAsRespondProvider(Org.Abstractions.Mapping mapping, Guid? guid = null, string? path = null)
private Guid? ConvertWireMockOrgMappingAndRegisterAsRespondProvider(OrgMapping mapping, Guid? guid = null, string? path = null)
{
var requestBuilder = Request.Create();

View File

@@ -1,19 +1,17 @@
// Copyright © WireMock.Net
using System.Net;
#if OPENAPIPARSER
using System;
using System.Linq;
using System.Net;
using Microsoft.AspNetCore.Http;
using WireMock.Net.OpenApiParser;
#endif
namespace WireMock.Server;
public partial class WireMockServer
{
private IResponseMessage OpenApiConvertToMappings(IRequestMessage requestMessage)
private IResponseMessage OpenApiConvertToMappings(HttpContext _, IRequestMessage requestMessage)
{
#if OPENAPIPARSER
try
{
var mappingModels = new WireMockOpenApiParser().FromText(requestMessage.Body!, out var diagnostic);
@@ -24,14 +22,10 @@ public partial class WireMockServer
_settings.Logger.Error("HttpStatusCode set to {0} {1}", HttpStatusCode.BadRequest, e);
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, e.Message);
}
#else
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, "Not supported for .NETStandard 1.3 and .NET 4.6.x or lower.");
#endif
}
private IResponseMessage OpenApiSaveToMappings(IRequestMessage requestMessage)
private IResponseMessage OpenApiSaveToMappings(HttpContext _, IRequestMessage requestMessage)
{
#if OPENAPIPARSER
try
{
var mappingModels = new WireMockOpenApiParser().FromText(requestMessage.Body!, out var diagnostic);
@@ -49,8 +43,5 @@ public partial class WireMockServer
_settings.Logger.Error("HttpStatusCode set to {0} {1}", HttpStatusCode.BadRequest, e);
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, e.Message);
}
#else
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, "Not supported for .NETStandard 1.3 and .NET 4.6.x or lower.");
#endif
}
}

View File

@@ -3,6 +3,7 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using WireMock.Constants;
using WireMock.Http;
using WireMock.Proxy;
@@ -33,7 +34,7 @@ public partial class WireMockServer
proxyRespondProvider.AtPriority(WireMockConstants.ProxyPriority);
}
if(settings.ProxyAndRecordSettings.ProxyAll)
if (settings.ProxyAndRecordSettings.ProxyAll)
{
proxyRespondProvider.AtPriority(int.MinValue);
}
@@ -41,7 +42,7 @@ public partial class WireMockServer
proxyRespondProvider.RespondWith(new ProxyAsyncResponseProvider(ProxyAndRecordAsync, settings));
}
private async Task<IResponseMessage> ProxyAndRecordAsync(IRequestMessage requestMessage, WireMockServerSettings settings)
private async Task<IResponseMessage> ProxyAndRecordAsync(HttpContext _, IRequestMessage requestMessage, WireMockServerSettings settings)
{
var requestUri = new Uri(requestMessage.Url);
var proxyUri = new Uri(settings.ProxyAndRecordSettings!.Url);

View File

@@ -0,0 +1,77 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Threading.Tasks;
using JetBrains.Annotations;
using WireMock.WebSockets;
namespace WireMock.Server;
public partial class WireMockServer
{
/// <summary>
/// Get all active WebSocket connections
/// </summary>
[PublicAPI]
public IReadOnlyCollection<WireMockWebSocketContext> GetWebSocketConnections()
{
return _options.WebSocketRegistries.Values
.SelectMany(r => r.GetConnections())
.ToList();
}
/// <summary>
/// Get WebSocket connections for a specific mapping
/// </summary>
[PublicAPI]
public IReadOnlyCollection<WireMockWebSocketContext> GetWebSocketConnections(Guid mappingGuid)
{
return _options.WebSocketRegistries.TryGetValue(mappingGuid, out var registry) ? registry.GetConnections() : [];
}
/// <summary>
/// Close a specific WebSocket connection
/// </summary>
[PublicAPI]
public async Task CloseWebSocketConnectionAsync(
Guid connectionId,
WebSocketCloseStatus closeStatus = WebSocketCloseStatus.NormalClosure,
string statusDescription = "Closed by server")
{
foreach (var registry in _options.WebSocketRegistries.Values)
{
if (registry.TryGetConnection(connectionId, out var connection))
{
await connection.CloseAsync(closeStatus, statusDescription);
return;
}
}
}
/// <summary>
/// Broadcast a message to all WebSocket connections in a specific mapping
/// </summary>
[PublicAPI]
public async Task BroadcastToWebSocketsAsync(Guid mappingGuid, string text)
{
if (_options.WebSocketRegistries.TryGetValue(mappingGuid, out var registry))
{
await registry.BroadcastTextAsync(text);
}
}
/// <summary>
/// Broadcast a message to all WebSocket connections
/// </summary>
[PublicAPI]
public async Task BroadcastToAllWebSocketsAsync(string text)
{
foreach (var registry in _options.WebSocketRegistries.Values)
{
await registry.BroadcastTextAsync(text);
}
}
}

View File

@@ -39,7 +39,7 @@ public partial class WireMockServer : IWireMockServer
private const int ServerStartDelayInMs = 100;
private readonly WireMockServerSettings _settings;
private readonly IOwinSelfHost? _httpServer;
private readonly AspNetCoreSelfHost? _httpServer;
private readonly IWireMockMiddlewareOptions _options = new WireMockMiddlewareOptions();
private readonly MappingConverter _mappingConverter;
private readonly MatcherMapper _matcherMapper;
@@ -62,7 +62,7 @@ public partial class WireMockServer : IWireMockServer
/// <inheritdoc />
[PublicAPI]
public int Port => Ports?.FirstOrDefault() ?? default;
public int Port => Ports?.FirstOrDefault() ?? 0;
/// <inheritdoc />
[PublicAPI]
@@ -411,11 +411,12 @@ public partial class WireMockServer : IWireMockServer
_dateTimeUtils
);
#if USE_ASPNETCORE
_options.AdditionalServiceRegistration = _settings.AdditionalServiceRegistration;
_options.CorsPolicyOptions = _settings.CorsPolicyOptions;
_options.ClientCertificateMode = (Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode)_settings.ClientCertificateMode;
_options.AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate;
_httpServer = new AspNetCoreSelfHost(_options, urlOptions);
#else
_httpServer = new OwinSelfHost(_options, urlOptions);
#endif
var startTask = _httpServer.StartAsync();
using (var ctsStartTimeout = new CancellationTokenSource(settings.StartTimeout))
@@ -470,7 +471,7 @@ public partial class WireMockServer : IWireMockServer
Given(Request.Create().WithPath("/*").UsingAnyMethod())
.WithGuid(Guid.Parse("90008000-0000-4444-a17e-669cd84f1f05"))
.AtPriority(1000)
.RespondWith(new DynamicResponseProvider(_ => ResponseMessageBuilder.Create(HttpStatusCode.NotFound, WireMockConstants.NoMatchingFound)));
.RespondWith(new DynamicResponseProvider((_, _) => ResponseMessageBuilder.Create(HttpStatusCode.NotFound, WireMockConstants.NoMatchingFound)));
}
/// <inheritdoc cref="IWireMockServer.Reset" />
@@ -536,15 +537,15 @@ public partial class WireMockServer : IWireMockServer
Guard.NotNull(tenant);
Guard.NotNull(audience);
#if NETSTANDARD1_3
throw new NotSupportedException("AzureADAuthentication is not supported for NETStandard 1.3");
#else
//#if NETSTANDARD1_3
// throw new NotSupportedException("AzureADAuthentication is not supported for NETStandard 1.3");
//#else
_options.AuthenticationMatcher = new AzureADAuthenticationMatcher(
new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler(),
new Microsoft.IdentityModel.Protocols.ConfigurationManager<Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfiguration>($"https://login.microsoftonline.com/{tenant}/.well-known/openid-configuration", new Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfigurationRetriever()),
tenant,
audience);
#endif
//#endif
}
/// <inheritdoc cref="IWireMockServer.SetBasicAuthentication(string, string)" />

View File

@@ -7,6 +7,7 @@ using System.Globalization;
using System.Linq;
using JetBrains.Annotations;
using Stef.Validation;
using WireMock.Constants;
using WireMock.Logging;
using WireMock.Models;
using WireMock.Types;
@@ -86,6 +87,7 @@ public static class WireMockServerSettingsParser
ParseCertificateSettings(settings, parser);
ParseHandlebarsSettings(settings, parser);
ParseActivityTracingSettings(settings, parser);
ParseWebSocketSettings(settings, parser);
return true;
}
@@ -242,4 +244,20 @@ public static class WireMockServerSettingsParser
};
}
}
private static void ParseWebSocketSettings(WireMockServerSettings settings, SimpleSettingsParser parser)
{
// Check if any WebSocket setting is present
if (parser.ContainsAny(
nameof(WebSocketSettings) + '.' + nameof(WebSocketSettings.MaxConnections),
nameof(WebSocketSettings) + '.' + nameof(WebSocketSettings.KeepAliveIntervalSeconds))
)
{
settings.WebSocketSettings = new WebSocketSettings
{
MaxConnections = parser.GetIntValue(nameof(WebSocketSettings) + '.' + nameof(WebSocketSettings.MaxConnections), 100),
KeepAliveIntervalSeconds = parser.GetIntValue(nameof(WebSocketSettings) + '.' + nameof(WebSocketSettings.KeepAliveIntervalSeconds), WebSocketConstants.DefaultKeepAliveIntervalSeconds),
};
}
}
}

View File

@@ -24,7 +24,7 @@ internal static class WireMockHandlebarsHelpers
GetBaseDirectory(),
};
#if !NETSTANDARD1_3_OR_GREATER
//#if !NETSTANDARD1_3_OR_GREATER
void Add(string? path, ICollection<string> customHelperPaths)
{
if (!string.IsNullOrEmpty(path))
@@ -36,7 +36,7 @@ internal static class WireMockHandlebarsHelpers
Add(Path.GetDirectoryName(System.Reflection.Assembly.GetCallingAssembly().Location), paths);
Add(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), paths);
Add(Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName), paths);
#endif
//#endif
o.CustomHelperPaths = paths;
o.Categories = settings.HandlebarsSettings?.AllowedHandlebarsHelpers ?? HandlebarsSettings.DefaultAllowedHandlebarsHelpers;
@@ -51,7 +51,7 @@ internal static class WireMockHandlebarsHelpers
private static string GetBaseDirectory()
{
#if NETSTANDARD1_3_OR_GREATER || NET6_0_OR_GREATER
#if NET8_0_OR_GREATER
return AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar);
#else
return AppDomain.CurrentDomain.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar);

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