Skip to content

Commit 98a0f2f

Browse files
StefHmattiskingMatt Philmon
authored
WebHook : UseFireAndForget + Delay (#803)
* UseFireAndForget * ... * delay * async * updated code accorsing to proposal * Change nuget to package reference for WireMock.Net.Console.Net472.Classic, move the new FireAndForget into the main mapping, out of individual webhook mappings making it all or nothing, update tests, change Middleware to await or not the firing of all webhooks. Update models as needed. (#804) Co-authored-by: Matt Philmon <[email protected]> * small update * Tweak middleware and fix bug in example (#806) Co-authored-by: Matt Philmon <[email protected]> * .ConfigureAwait(false) Co-authored-by: mattisking <[email protected]> Co-authored-by: Matt Philmon <[email protected]>
1 parent 13a06b9 commit 98a0f2f

File tree

22 files changed

+634
-424
lines changed

22 files changed

+634
-424
lines changed

examples/WireMock.Net.Console.Net452.Classic/MainApp.cs

+35
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Newtonsoft.Json;
77
using WireMock.Logging;
88
using WireMock.Matchers;
9+
using WireMock.Models;
910
using WireMock.RequestBuilders;
1011
using WireMock.ResponseBuilders;
1112
using WireMock.Server;
@@ -576,6 +577,40 @@ public static void Run()
576577
};
577578
}));
578579

580+
server.Given(Request.Create().WithPath(new WildcardMatcher("/multi-webhook", true)).UsingPost())
581+
.WithWebhook(new[] {
582+
new Webhook()
583+
{
584+
Request = new WebhookRequest
585+
{
586+
Url = "http://localhost:12345/foo1",
587+
Method = "post",
588+
BodyData = new BodyData
589+
{
590+
BodyAsString = "OK 1!", DetectedBodyType = BodyType.String
591+
},
592+
Delay = 1000
593+
}
594+
},
595+
new Webhook()
596+
{
597+
Request = new WebhookRequest
598+
{
599+
Url = "http://localhost:12345/foo2",
600+
Method = "post",
601+
BodyData = new BodyData
602+
{
603+
BodyAsString = "OK 2!",
604+
DetectedBodyType = BodyType.String
605+
},
606+
MinimumRandomDelay = 3000,
607+
MaximumRandomDelay = 7000
608+
}
609+
}
610+
})
611+
.WithWebhookFireAndForget(true)
612+
.RespondWith(Response.Create().WithBody("a-response"));
613+
579614
System.Console.WriteLine(JsonConvert.SerializeObject(server.MappingModels, Formatting.Indented));
580615

581616
System.Console.WriteLine("Press any key to stop the server");

examples/WireMock.Net.Console.Net472.Classic/WireMock.Net.Console.Net472.Classic.csproj

+10-9
Original file line numberDiff line numberDiff line change
@@ -342,15 +342,6 @@
342342
<Reference Include="TinyMapper, Version=3.0.1.0, Culture=neutral, processorArchitecture=MSIL">
343343
<HintPath>..\..\packages\TinyMapper.3.0.3\lib\net40\TinyMapper.dll</HintPath>
344344
</Reference>
345-
<Reference Include="WireMock.Net, Version=1.5.3.0, Culture=neutral, PublicKeyToken=c8d65537854e1f03, processorArchitecture=MSIL">
346-
<HintPath>..\..\packages\WireMock.Net.1.5.3\lib\net461\WireMock.Net.dll</HintPath>
347-
</Reference>
348-
<Reference Include="WireMock.Net.Abstractions, Version=1.5.3.0, Culture=neutral, PublicKeyToken=c8d65537854e1f03, processorArchitecture=MSIL">
349-
<HintPath>..\..\packages\WireMock.Net.Abstractions.1.5.3\lib\net451\WireMock.Net.Abstractions.dll</HintPath>
350-
</Reference>
351-
<Reference Include="WireMock.Org.Abstractions, Version=1.5.3.0, Culture=neutral, PublicKeyToken=c8d65537854e1f03, processorArchitecture=MSIL">
352-
<HintPath>..\..\packages\WireMock.Org.Abstractions.1.5.3\lib\net45\WireMock.Org.Abstractions.dll</HintPath>
353-
</Reference>
354345
<Reference Include="XPath2, Version=1.1.3.0, Culture=neutral, PublicKeyToken=463c6d7fb740c7e5, processorArchitecture=MSIL">
355346
<HintPath>..\..\packages\XPath2.1.1.3\lib\net452\XPath2.dll</HintPath>
356347
</Reference>
@@ -388,6 +379,16 @@
388379
<None Include="App.config" />
389380
<None Include="packages.config" />
390381
</ItemGroup>
382+
<ItemGroup>
383+
<ProjectReference Include="..\..\src\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj">
384+
<Project>{b6269aac-170a-4346-8b9a-579ded3d9a94}</Project>
385+
<Name>WireMock.Net.Abstractions</Name>
386+
</ProjectReference>
387+
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj">
388+
<Project>{d3804228-91f4-4502-9595-39584e5a01ad}</Project>
389+
<Name>WireMock.Net</Name>
390+
</ProjectReference>
391+
</ItemGroup>
391392
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
392393
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
393394
<PropertyGroup>

examples/WireMock.Net.Console.Net472.Classic/packages.config

-3
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,6 @@
147147
<package id="System.Threading.Tasks.Extensions" version="4.5.1" targetFramework="net472" />
148148
<package id="System.ValueTuple" version="4.5.0" targetFramework="net472" />
149149
<package id="TinyMapper" version="3.0.3" targetFramework="net472" />
150-
<package id="WireMock.Net" version="1.5.3" targetFramework="net472" />
151-
<package id="WireMock.Net.Abstractions" version="1.5.3" targetFramework="net472" />
152-
<package id="WireMock.Org.Abstractions" version="1.5.3" targetFramework="net472" />
153150
<package id="XPath2" version="1.1.3" targetFramework="net472" />
154151
<package id="XPath2.Extensions" version="1.1.3" targetFramework="net472" />
155152
</packages>

src/WireMock.Net.Abstractions/Admin/Mappings/MappingModel.cs

+5
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,9 @@ public class MappingModel
7474
/// The Webhooks.
7575
/// </summary>
7676
public WebhookModel[]? Webhooks { get; set; }
77+
78+
/// <summary>
79+
/// Fire and forget for webhooks.
80+
/// </summary>
81+
public bool? UseWebhooksFireAndForget { get; set; }
7782
}
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
namespace WireMock.Admin.Mappings
1+
namespace WireMock.Admin.Mappings;
2+
3+
/// <summary>
4+
/// The Webhook
5+
/// </summary>
6+
[FluentBuilder.AutoGenerateBuilder]
7+
public class WebhookModel
28
{
39
/// <summary>
4-
/// The Webhook
10+
/// The Webhook Request.
511
/// </summary>
6-
[FluentBuilder.AutoGenerateBuilder]
7-
public class WebhookModel
8-
{
9-
/// <summary>
10-
/// The Webhook Request.
11-
/// </summary>
12-
public WebhookRequestModel Request { get; set; }
13-
}
12+
public WebhookRequestModel Request { get; set; } = null!;
1413
}

src/WireMock.Net.Abstractions/Admin/Mappings/WebhookRequestModel.cs

+17-2
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ public class WebhookRequestModel
1111
/// <summary>
1212
/// Gets or sets the Url.
1313
/// </summary>
14-
public string Url { get; set; }
14+
public string Url { get; set; } = null!;
1515

1616
/// <summary>
1717
/// The method
1818
/// </summary>
19-
public string Method { get; set; }
19+
public string Method { get; set; } = null!;
2020

2121
/// <summary>
2222
/// Gets or sets the headers.
@@ -47,4 +47,19 @@ public class WebhookRequestModel
4747
/// The ReplaceNodeOptions to use when transforming a JSON node.
4848
/// </summary>
4949
public string? TransformerReplaceNodeOptions { get; set; }
50+
51+
/// <summary>
52+
/// Gets or sets the delay in milliseconds.
53+
/// </summary>
54+
public int? Delay { get; set; }
55+
56+
/// <summary>
57+
/// Gets or sets the minimum random delay in milliseconds.
58+
/// </summary>
59+
public int? MinimumRandomDelay { get; set; }
60+
61+
/// <summary>
62+
/// Gets or sets the maximum random delay in milliseconds.
63+
/// </summary>
64+
public int? MaximumRandomDelay { get; set; }
5065
}

src/WireMock.Net.Abstractions/Models/IWebhookRequest.cs

+54-40
Original file line numberDiff line numberDiff line change
@@ -2,46 +2,60 @@
22
using WireMock.Types;
33
using WireMock.Util;
44

5-
namespace WireMock.Models
5+
namespace WireMock.Models;
6+
7+
/// <summary>
8+
/// IWebhookRequest
9+
/// </summary>
10+
public interface IWebhookRequest
611
{
712
/// <summary>
8-
/// IWebhookRequest
9-
/// </summary>
10-
public interface IWebhookRequest
11-
{
12-
/// <summary>
13-
/// The Webhook Url.
14-
/// </summary>
15-
string Url { get; set; }
16-
17-
/// <summary>
18-
/// The method to use.
19-
/// </summary>
20-
string Method { get; set; }
21-
22-
/// <summary>
23-
/// The Headers to send.
24-
/// </summary>
25-
IDictionary<string, WireMockList<string>>? Headers { get; }
26-
27-
/// <summary>
28-
/// The body to send.
29-
/// </summary>
30-
IBodyData? BodyData { get; set; }
31-
32-
/// <summary>
33-
/// Use Transformer.
34-
/// </summary>
35-
bool? UseTransformer { get; set; }
36-
37-
/// <summary>
38-
/// The transformer type.
39-
/// </summary>
40-
TransformerType TransformerType { get; set; }
41-
42-
/// <summary>
43-
/// The ReplaceNodeOptions to use when transforming a JSON node.
44-
/// </summary>
45-
ReplaceNodeOptions TransformerReplaceNodeOptions { get; set; }
46-
}
13+
/// The Webhook Url.
14+
/// </summary>
15+
string Url { get; set; }
16+
17+
/// <summary>
18+
/// The method to use.
19+
/// </summary>
20+
string Method { get; set; }
21+
22+
/// <summary>
23+
/// The Headers to send.
24+
/// </summary>
25+
IDictionary<string, WireMockList<string>>? Headers { get; }
26+
27+
/// <summary>
28+
/// The body to send.
29+
/// </summary>
30+
IBodyData? BodyData { get; set; }
31+
32+
/// <summary>
33+
/// Use Transformer.
34+
/// </summary>
35+
bool? UseTransformer { get; set; }
36+
37+
/// <summary>
38+
/// The transformer type.
39+
/// </summary>
40+
TransformerType TransformerType { get; set; }
41+
42+
/// <summary>
43+
/// The ReplaceNodeOptions to use when transforming a JSON node.
44+
/// </summary>
45+
ReplaceNodeOptions TransformerReplaceNodeOptions { get; set; }
46+
47+
/// <summary>
48+
/// Gets or sets the delay in milliseconds.
49+
/// </summary>
50+
int? Delay { get; set; }
51+
52+
/// <summary>
53+
/// Gets or sets the minimum random delay in milliseconds.
54+
/// </summary>
55+
int? MinimumRandomDelay { get; set; }
56+
57+
/// <summary>
58+
/// Gets or sets the maximum random delay in milliseconds.
59+
/// </summary>
60+
int? MaximumRandomDelay { get; set; }
4761
}
+43-13
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Diagnostics.CodeAnalysis;
34
using System.Linq;
45
using System.Net.Http;
6+
using System.Threading;
57
using System.Threading.Tasks;
68
using Stef.Validation;
79
using WireMock.Models;
@@ -17,6 +19,7 @@ namespace WireMock.Http;
1719
internal class WebhookSender
1820
{
1921
private const string ClientIp = "::1";
22+
private static readonly ThreadLocal<Random> Random = new(() => new Random(DateTime.UtcNow.Millisecond));
2023

2124
private readonly WireMockServerSettings _settings;
2225

@@ -25,20 +28,26 @@ public WebhookSender(WireMockServerSettings settings)
2528
_settings = Guard.NotNull(settings);
2629
}
2730

28-
public Task<HttpResponseMessage> SendAsync(HttpClient client, IMapping mapping, IWebhookRequest request, IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage)
31+
public async Task<HttpResponseMessage> SendAsync(
32+
HttpClient client,
33+
IMapping mapping,
34+
IWebhookRequest webhookRequest,
35+
IRequestMessage originalRequestMessage,
36+
IResponseMessage originalResponseMessage
37+
)
2938
{
3039
Guard.NotNull(client);
3140
Guard.NotNull(mapping);
32-
Guard.NotNull(request);
41+
Guard.NotNull(webhookRequest);
3342
Guard.NotNull(originalRequestMessage);
3443
Guard.NotNull(originalResponseMessage);
3544

3645
IBodyData? bodyData;
3746
IDictionary<string, WireMockList<string>>? headers;
38-
if (request.UseTransformer == true)
47+
if (webhookRequest.UseTransformer == true)
3948
{
4049
ITransformer responseMessageTransformer;
41-
switch (request.TransformerType)
50+
switch (webhookRequest.TransformerType)
4251
{
4352
case TransformerType.Handlebars:
4453
var factoryHandlebars = new HandlebarsContextFactory(_settings.FileSystemHandler, _settings.HandlebarsRegistrationCallback);
@@ -47,26 +56,26 @@ public Task<HttpResponseMessage> SendAsync(HttpClient client, IMapping mapping,
4756

4857
case TransformerType.Scriban:
4958
case TransformerType.ScribanDotLiquid:
50-
var factoryDotLiquid = new ScribanContextFactory(_settings.FileSystemHandler, request.TransformerType);
59+
var factoryDotLiquid = new ScribanContextFactory(_settings.FileSystemHandler, webhookRequest.TransformerType);
5160
responseMessageTransformer = new Transformer(factoryDotLiquid);
5261
break;
5362

5463
default:
55-
throw new NotImplementedException($"TransformerType '{request.TransformerType}' is not supported.");
64+
throw new NotImplementedException($"TransformerType '{webhookRequest.TransformerType}' is not supported.");
5665
}
5766

58-
(bodyData, headers) = responseMessageTransformer.Transform(mapping, originalRequestMessage, originalResponseMessage, request.BodyData, request.Headers, request.TransformerReplaceNodeOptions);
67+
(bodyData, headers) = responseMessageTransformer.Transform(mapping, originalRequestMessage, originalResponseMessage, webhookRequest.BodyData, webhookRequest.Headers, webhookRequest.TransformerReplaceNodeOptions);
5968
}
6069
else
6170
{
62-
bodyData = request.BodyData;
63-
headers = request.Headers;
71+
bodyData = webhookRequest.BodyData;
72+
headers = webhookRequest.Headers;
6473
}
6574

6675
// Create RequestMessage
6776
var requestMessage = new RequestMessage(
68-
new UrlDetails(request.Url),
69-
request.Method,
77+
new UrlDetails(webhookRequest.Url),
78+
webhookRequest.Method,
7079
ClientIp,
7180
bodyData,
7281
headers?.ToDictionary(x => x.Key, x => x.Value.ToArray())
@@ -76,9 +85,30 @@ public Task<HttpResponseMessage> SendAsync(HttpClient client, IMapping mapping,
7685
};
7786

7887
// Create HttpRequestMessage
79-
var httpRequestMessage = HttpRequestMessageHelper.Create(requestMessage, request.Url);
88+
var httpRequestMessage = HttpRequestMessageHelper.Create(requestMessage, webhookRequest.Url);
89+
90+
// Delay (if required)
91+
if (TryGetDelay(webhookRequest, out var delay))
92+
{
93+
await Task.Delay(delay.Value).ConfigureAwait(false);
94+
}
8095

8196
// Call the URL
82-
return client.SendAsync(httpRequestMessage);
97+
return await client.SendAsync(httpRequestMessage).ConfigureAwait(false);
98+
}
99+
100+
private static bool TryGetDelay(IWebhookRequest webhookRequest, [NotNullWhen(true)] out int? delay)
101+
{
102+
delay = webhookRequest.Delay;
103+
var minimumDelay = webhookRequest.MinimumRandomDelay;
104+
var maximumDelay = webhookRequest.MaximumRandomDelay;
105+
106+
if (minimumDelay is not null && maximumDelay is not null && maximumDelay >= minimumDelay)
107+
{
108+
delay = Random.Value!.Next(minimumDelay.Value, maximumDelay.Value);
109+
return true;
110+
}
111+
112+
return delay is not null;
83113
}
84114
}

src/WireMock.Net/IMapping.cs

+5
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ public interface IMapping
112112
/// </summary>
113113
IWebhook[]? Webhooks { get; }
114114

115+
/// <summary>
116+
/// Use Fire and Forget for the defined webhook(s). [Optional]
117+
/// </summary>
118+
public bool? UseWebhooksFireAndForget { get; set; }
119+
115120
/// <summary>
116121
/// ProvideResponseAsync
117122
/// </summary>

0 commit comments

Comments
 (0)