Add output caching to the Delivery API (#15216)

This commit is contained in:
Kenn Jacobsen
2023-11-20 11:01:36 +01:00
committed by GitHub
parent 0085f9862e
commit a4c7047a50
7 changed files with 147 additions and 1 deletions

View File

@@ -0,0 +1,32 @@
using Microsoft.AspNetCore.OutputCaching;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.DeliveryApi;
namespace Umbraco.Cms.Api.Delivery.Caching;
internal sealed class DeliveryApiOutputCachePolicy : IOutputCachePolicy
{
private readonly TimeSpan _duration;
public DeliveryApiOutputCachePolicy(TimeSpan duration)
=> _duration = duration;
ValueTask IOutputCachePolicy.CacheRequestAsync(OutputCacheContext context, CancellationToken cancellationToken)
{
IRequestPreviewService requestPreviewService = context
.HttpContext
.RequestServices
.GetRequiredService<IRequestPreviewService>();
context.EnableOutputCaching = requestPreviewService.IsPreview() is false;
context.ResponseExpirationTimeSpan = _duration;
return ValueTask.CompletedTask;
}
ValueTask IOutputCachePolicy.ServeFromCacheAsync(OutputCacheContext context, CancellationToken cancellationToken)
=> ValueTask.CompletedTask;
ValueTask IOutputCachePolicy.ServeResponseAsync(OutputCacheContext context, CancellationToken cancellationToken)
=> ValueTask.CompletedTask;
}

View File

@@ -0,0 +1,14 @@
using Microsoft.AspNetCore.Builder;
using Umbraco.Cms.Web.Common.ApplicationBuilder;
namespace Umbraco.Cms.Api.Delivery.Caching;
internal sealed class OutputCachePipelineFilter : UmbracoPipelineFilter
{
public OutputCachePipelineFilter(string name)
: base(name)
=> PostPipeline = PostPipelineAction;
private void PostPipelineAction(IApplicationBuilder applicationBuilder)
=> applicationBuilder.UseOutputCache();
}

View File

@@ -1,8 +1,10 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OutputCaching;
using Umbraco.Cms.Api.Common.Builders;
using Umbraco.Cms.Api.Delivery.Filters;
using Umbraco.Cms.Api.Delivery.Routing;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.Services.OperationStatus;
@@ -13,6 +15,7 @@ namespace Umbraco.Cms.Api.Delivery.Controllers.Content;
[ApiExplorerSettings(GroupName = "Content")]
[LocalizeFromAcceptLanguageHeader]
[ValidateStartItem]
[OutputCache(PolicyName = Constants.DeliveryApi.OutputCache.ContentCachePolicy)]
public abstract class ContentApiControllerBase : DeliveryApiControllerBase
{
protected IApiPublishedContentCache ApiPublishedContentCache { get; }

View File

@@ -1,7 +1,9 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OutputCaching;
using Umbraco.Cms.Api.Common.Builders;
using Umbraco.Cms.Api.Delivery.Filters;
using Umbraco.Cms.Api.Delivery.Routing;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models.DeliveryApi;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
@@ -14,6 +16,7 @@ namespace Umbraco.Cms.Api.Delivery.Controllers.Media;
[DeliveryApiMediaAccess]
[VersionedDeliveryApiRoute("media")]
[ApiExplorerSettings(GroupName = "Media")]
[OutputCache(PolicyName = Constants.DeliveryApi.OutputCache.MediaCachePolicy)]
public abstract class MediaApiControllerBase : DeliveryApiControllerBase
{
private readonly IApiMediaWithCropsResponseBuilder _apiMediaWithCropsResponseBuilder;

View File

@@ -3,9 +3,11 @@ using System.Text.Json.Serialization;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Common.DependencyInjection;
using Umbraco.Cms.Api.Delivery.Accessors;
using Umbraco.Cms.Api.Delivery.Caching;
using Umbraco.Cms.Api.Delivery.Configuration;
using Umbraco.Cms.Api.Delivery.Handlers;
using Umbraco.Cms.Api.Delivery.Json;
@@ -14,10 +16,12 @@ using Umbraco.Cms.Api.Delivery.Routing;
using Umbraco.Cms.Api.Delivery.Security;
using Umbraco.Cms.Api.Delivery.Services;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Infrastructure.Security;
using Umbraco.Cms.Web.Common.ApplicationBuilder;
namespace Umbraco.Extensions;
@@ -79,7 +83,38 @@ public static class UmbracoBuilderExtensions
// FIXME: remove this when Delivery API V1 is removed
builder.Services.AddSingleton<MatcherPolicy, DeliveryApiItemsEndpointsMatcherPolicy>();
builder.AddOutputCache();
return builder;
}
private static IUmbracoBuilder AddOutputCache(this IUmbracoBuilder builder)
{
DeliveryApiSettings.OutputCacheSettings outputCacheSettings =
builder.Config.GetSection(Constants.Configuration.ConfigDeliveryApi).Get<DeliveryApiSettings>()?.OutputCache
?? new DeliveryApiSettings.OutputCacheSettings();
if (outputCacheSettings.Enabled is false || outputCacheSettings is { ContentDuration.TotalSeconds: <= 0, MediaDuration.TotalSeconds: <= 0 })
{
return builder;
}
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(_ => { });
if (outputCacheSettings.ContentDuration.TotalSeconds > 0)
{
options.AddPolicy(Constants.DeliveryApi.OutputCache.ContentCachePolicy, new DeliveryApiOutputCachePolicy(outputCacheSettings.ContentDuration));
}
if (outputCacheSettings.MediaDuration.TotalSeconds > 0)
{
options.AddPolicy(Constants.DeliveryApi.OutputCache.MediaCachePolicy, new DeliveryApiOutputCachePolicy(outputCacheSettings.MediaDuration));
}
});
builder.Services.Configure<UmbracoPipelineOptions>(options => options.AddFilter(new OutputCachePipelineFilter("UmbracoDeliveryApiOutputCache")));
return builder;
}
}

View File

@@ -59,6 +59,11 @@ public class DeliveryApiSettings
/// </summary>
public MemberAuthorizationSettings? MemberAuthorization { get; set; } = null;
/// <summary>
/// Gets or sets the settings for the Delivery API output cache.
/// </summary>
public OutputCacheSettings OutputCache { get; set; } = new ();
/// <summary>
/// Gets a value indicating if any member authorization type is enabled for the Delivery API.
/// </summary>
@@ -138,4 +143,42 @@ public class DeliveryApiSettings
/// <remarks>These are only required if logout is to be used.</remarks>
public Uri[] LogoutRedirectUrls { get; set; } = Array.Empty<Uri>();
}
/// <summary>
/// Typed configuration options for output caching of the Delivery API.
/// </summary>
public class OutputCacheSettings
{
private const string StaticDuration = "00:01:00"; // one minute
/// <summary>
/// Gets or sets a value indicating whether the Delivery API output should be cached.
/// </summary>
/// <value><c>true</c> if the Delivery API output should be cached; otherwise, <c>false</c>.</value>
/// <remarks>
/// The default value is <c>false</c>.
/// </remarks>
[DefaultValue(StaticEnabled)]
public bool Enabled { get; set; } = StaticEnabled;
/// <summary>
/// Gets or sets a value indicating how long the Content Delivery API output should be cached.
/// </summary>
/// <value>Cache lifetime.</value>
/// <remarks>
/// The default cache duration is one minute, if this configuration value is not provided.
/// </remarks>
[DefaultValue(StaticDuration)]
public TimeSpan ContentDuration { get; set; } = TimeSpan.Parse(StaticDuration);
/// <summary>
/// Gets or sets a value indicating how long the Media Delivery API output should be cached.
/// </summary>
/// <value>Cache lifetime.</value>
/// <remarks>
/// The default cache duration is one minute, if this configuration value is not provided.
/// </remarks>
[DefaultValue(StaticDuration)]
public TimeSpan MediaDuration { get; set; } = TimeSpan.Parse(StaticDuration);
}
}

View File

@@ -17,5 +17,21 @@ public static partial class Constants
/// </summary>
public const string PreviewContentPathPrefix = "preview-";
}
/// <summary>
/// Constants for Delivery API output cache.
/// </summary>
public static class OutputCache
{
/// <summary>
/// Output cache policy name for content
/// </summary>
public const string ContentCachePolicy = "DeliveryApiContent";
/// <summary>
/// Output cache policy name for media
/// </summary>
public const string MediaCachePolicy = "DeliveryApiMedia";
}
}
}