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; }