diff --git a/src/Umbraco.Core/Constants-HttpClients.cs b/src/Umbraco.Core/Constants-HttpClients.cs
index 677f442085..a9c693f39d 100644
--- a/src/Umbraco.Core/Constants-HttpClients.cs
+++ b/src/Umbraco.Core/Constants-HttpClients.cs
@@ -14,5 +14,18 @@ public static partial class Constants
/// Name for http client which ignores certificate errors.
///
public const string IgnoreCertificateErrors = "Umbraco:HttpClients:IgnoreCertificateErrors";
+
+ ///
+ /// Name for http client which sends webhook requests.
+ ///
+ public const string WebhookFiring = "Umbraco:HttpClients:WebhookFiring";
+
+ public static class Headers
+ {
+ ///
+ /// User agent name for the product name.
+ ///
+ public const string UserAgentProductName = "Umbraco-Cms";
+ }
}
}
diff --git a/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs b/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs
index 9385dcf6c9..59d0f171ef 100644
--- a/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs
+++ b/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs
@@ -55,7 +55,7 @@ public abstract class OEmbedProviderBase : IEmbedProvider
if (_httpClient == null)
{
_httpClient = new HttpClient();
- _httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd("Umbraco-CMS");
+ _httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd(Constants.HttpClients.Headers.UserAgentProductName);
}
using (var request = new HttpRequestMessage(HttpMethod.Get, url))
diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs
index 11eaa3bb8a..3d16960144 100644
--- a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs
+++ b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs
@@ -6,7 +6,6 @@ using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Scoping;
-using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs;
@@ -15,11 +14,11 @@ public class WebhookFiring : IRecurringBackgroundJob
{
private readonly ILogger _logger;
private readonly IWebhookRequestService _webhookRequestService;
- private readonly IJsonSerializer _jsonSerializer;
private readonly IWebhookLogFactory _webhookLogFactory;
private readonly IWebhookLogService _webhookLogService;
private readonly IWebhookService _webHookService;
private readonly ICoreScopeProvider _coreScopeProvider;
+ private readonly IHttpClientFactory _httpClientFactory;
private WebhookSettings _webhookSettings;
public TimeSpan Period => _webhookSettings.Period;
@@ -32,20 +31,20 @@ public class WebhookFiring : IRecurringBackgroundJob
public WebhookFiring(
ILogger logger,
IWebhookRequestService webhookRequestService,
- IJsonSerializer jsonSerializer,
IWebhookLogFactory webhookLogFactory,
IWebhookLogService webhookLogService,
IWebhookService webHookService,
IOptionsMonitor webhookSettings,
- ICoreScopeProvider coreScopeProvider)
+ ICoreScopeProvider coreScopeProvider,
+ IHttpClientFactory httpClientFactory)
{
_logger = logger;
_webhookRequestService = webhookRequestService;
- _jsonSerializer = jsonSerializer;
_webhookLogFactory = webhookLogFactory;
_webhookLogService = webhookLogService;
_webHookService = webHookService;
_coreScopeProvider = coreScopeProvider;
+ _httpClientFactory = httpClientFactory;
_webhookSettings = webhookSettings.CurrentValue;
webhookSettings.OnChange(x => _webhookSettings = x);
}
@@ -72,8 +71,7 @@ public class WebhookFiring : IRecurringBackgroundJob
return;
}
- HttpResponseMessage? response = await SendRequestAsync(webhook, request.EventAlias, request.RequestObject, request.RetryCount, CancellationToken.None);
-
+ using HttpResponseMessage? response = await SendRequestAsync(webhook, request.EventAlias, request.RequestObject, request.RetryCount, CancellationToken.None);
if ((response?.IsSuccessStatusCode ?? false) || request.RetryCount >= _webhookSettings.MaximumRetries)
{
await _webhookRequestService.DeleteAsync(request);
@@ -90,24 +88,36 @@ public class WebhookFiring : IRecurringBackgroundJob
private async Task SendRequestAsync(IWebhook webhook, string eventName, string? serializedObject, int retryCount, CancellationToken cancellationToken)
{
- using var httpClient = new HttpClient();
+ using HttpClient httpClient = _httpClientFactory.CreateClient(Constants.HttpClients.WebhookFiring);
- var stringContent = new StringContent(serializedObject ?? string.Empty, Encoding.UTF8, MediaTypeNames.Application.Json);
- stringContent.Headers.TryAddWithoutValidation("Umb-Webhook-Event", eventName);
-
- foreach (KeyValuePair header in webhook.Headers)
+ using var request = new HttpRequestMessage(HttpMethod.Post, webhook.Url)
{
- stringContent.Headers.TryAddWithoutValidation(header.Key, header.Value);
- }
+ Version = httpClient.DefaultRequestVersion,
+ VersionPolicy = httpClient.DefaultVersionPolicy,
+ };
HttpResponseMessage? response = null;
try
{
- response = await httpClient.PostAsync(webhook.Url, stringContent, cancellationToken);
+ // Add headers
+ request.Headers.Add("Umb-Webhook-Event", eventName);
+ request.Headers.Add("Umb-Webhook-RetryCount", retryCount.ToString());
+ request.Headers.Add("Umb-Webhook-Date", DateTime.Now.ToString("R"));
+
+ foreach (KeyValuePair header in webhook.Headers)
+ {
+ request.Headers.Add(header.Key, header.Value);
+ }
+
+ // Set content
+ request.Content = new StringContent(serializedObject ?? string.Empty, Encoding.UTF8, MediaTypeNames.Application.Json);
+
+ // Send request
+ response = await httpClient.SendAsync(request, cancellationToken);
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error while sending webhook request for webhook {WebhookKey}.", webhook);
+ _logger.LogError(ex, "Error while sending webhook request for webhook {WebhookKey}.", webhook.Key);
}
var webhookResponseModel = new WebhookResponseModel
diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs
index 40436fd798..8939b6fcb4 100644
--- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs
+++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs
@@ -1,4 +1,5 @@
using System.Data.Common;
+using System.Net.Http.Headers;
using System.Reflection;
using Dazinator.Extensions.FileProviders.GlobPatternFilter;
using Microsoft.AspNetCore.Builder;
@@ -10,7 +11,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
-using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Serilog.Extensions.Logging;
@@ -23,6 +23,7 @@ using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Blocks;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Composing;
+using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Diagnostics;
@@ -261,6 +262,11 @@ public static partial class UmbracoBuilderExtensions
ServerCertificateCustomValidationCallback =
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator,
});
+ builder.Services.AddHttpClient(Constants.HttpClients.WebhookFiring, (services, client) =>
+ {
+ var productVersion = services.GetRequiredService().SemanticVersion.ToSemanticStringWithoutBuild();
+ client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(Constants.HttpClients.Headers.UserAgentProductName, productVersion));
+ });
return builder;
}