Browse Source

Caching header improvements.

pull/492/head
Sebastian 6 years ago
parent
commit
882b0ea6fc
  1. 31
      backend/src/Squidex.Domain.Apps.Entities/Contents/ContextExtensions.cs
  2. 5
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs
  3. 6
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/IContentEnricherStep.cs
  4. 8
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichForCaching.cs
  5. 2
      backend/src/Squidex.Infrastructure/Caching/IRequestCache.cs
  6. 1
      backend/src/Squidex.Web/Pipeline/CachingFilter.cs
  7. 38
      backend/src/Squidex.Web/Pipeline/CachingManager.cs
  8. 14
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherTests.cs
  9. 24
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichForCachingTests.cs
  10. 47
      backend/tests/Squidex.Web.Tests/Pipeline/CachingFilterTests.cs

31
backend/src/Squidex.Domain.Apps.Entities/Contents/ContextExtensions.cs

@ -9,21 +9,36 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Caching;
#pragma warning disable IDE0060 // Remove unused parameter
namespace Squidex.Domain.Apps.Entities.Contents
{
public static class ContextExtensions
{
private const string HeaderUnpublished = "X-Unpublished";
private const string HeaderFlatten = "X-Flatten";
private const string HeaderLanguages = "X-Languages";
private const string HeaderResolveFlow = "X-ResolveFlow";
private const string HeaderResolveAssetUrls = "X-Resolve-Urls";
private const string HeaderNoResolveLanguages = "X-NoResolveLanguages";
private const string HeaderNoEnrichment = "X-NoEnrichment";
private const string HeaderNoCleanup = "X-NoCleanup";
private const string HeaderNoEnrichment = "X-NoEnrichment";
private const string HeaderNoResolveLanguages = "X-NoResolveLanguages";
private const string HeaderResolveFlow = "X-ResolveFlow";
private const string HeaderResolveUrls = "X-Resolve-Urls";
private const string HeaderUnpublished = "X-Unpublished";
private static readonly char[] Separators = { ',', ';' };
public static void AddCacheHeaders(this Context context, IRequestCache cache)
{
cache.AddHeader(HeaderFlatten);
cache.AddHeader(HeaderLanguages);
cache.AddHeader(HeaderNoCleanup);
cache.AddHeader(HeaderNoEnrichment);
cache.AddHeader(HeaderNoResolveLanguages);
cache.AddHeader(HeaderResolveFlow);
cache.AddHeader(HeaderResolveUrls);
cache.AddHeader(HeaderUnpublished);
}
public static bool ShouldCleanup(this Context context)
{
return !context.Headers.ContainsKey(HeaderNoCleanup);
@ -91,7 +106,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
public static IEnumerable<string> AssetUrls(this Context context)
{
if (context.Headers.TryGetValue(HeaderResolveAssetUrls, out var value))
if (context.Headers.TryGetValue(HeaderResolveUrls, out var value))
{
return value.Split(Separators, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToHashSet();
}
@ -103,11 +118,11 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
if (fieldNames?.Any() == true)
{
context.Headers[HeaderResolveAssetUrls] = string.Join(",", fieldNames);
context.Headers[HeaderResolveUrls] = string.Join(",", fieldNames);
}
else
{
context.Headers.Remove(HeaderResolveAssetUrls);
context.Headers.Remove(HeaderResolveUrls);
}
return context;

5
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs

@ -54,6 +54,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
var results = new List<ContentEntity>();
foreach (var step in steps)
{
await step.EnrichAsync(context);
}
if (contents.Any())
{
foreach (var content in contents)

6
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/IContentEnricherStep.cs

@ -9,6 +9,7 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
@ -17,5 +18,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
public interface IContentEnricherStep
{
Task EnrichAsync(Context context, IEnumerable<ContentEntity> contents, ProvideSchema schemas);
Task EnrichAsync(Context context)
{
return TaskHelper.Done;
}
}
}

8
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichForCaching.cs

@ -10,6 +10,7 @@ using System.Linq;
using System.Threading.Tasks;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Caching;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
{
@ -24,6 +25,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
this.requestCache = requestCache;
}
public Task EnrichAsync(Context context)
{
context.AddCacheHeaders(requestCache);
return TaskHelper.Done;
}
public async Task EnrichAsync(Context context, IEnumerable<ContentEntity> contents, ProvideSchema schemas)
{
var app = context.App;

2
backend/src/Squidex.Infrastructure/Caching/IRequestCache.cs

@ -14,5 +14,7 @@ namespace Squidex.Infrastructure.Caching
void AddDependency(Guid key, long version);
void AddDependency(object? value);
void AddHeader(string header);
}
}

1
backend/src/Squidex.Web/Pipeline/CachingFilter.cs

@ -35,6 +35,7 @@ namespace Squidex.Web.Pipeline
var httpContext = context.HttpContext;
cachingManager.Start(httpContext);
cachingManager.AddHeader(HeaderNames.Authorization);
var resultContext = await next();

38
backend/src/Squidex.Web/Pipeline/CachingManager.cs

@ -7,10 +7,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Caching;
@ -26,6 +28,7 @@ namespace Squidex.Web.Pipeline
{
private readonly IncrementalHash hasher = IncrementalHash.CreateHash(HashAlgorithmName.SHA256);
private readonly HashSet<string> keys = new HashSet<string>();
private readonly HashSet<string> headers = new HashSet<string>();
private readonly ReaderWriterLockSlim slimLock = new ReaderWriterLockSlim();
private bool hasDependency;
@ -99,6 +102,28 @@ namespace Squidex.Web.Pipeline
response.Headers.Add("Surrogate-Key", value);
}
if (headers.Count > 0)
{
response.Headers.Add(HeaderNames.Vary, new StringValues(headers.ToArray()));
}
}
public void AddHeader(string header)
{
if (!string.IsNullOrWhiteSpace(header))
{
try
{
slimLock.EnterWriteLock();
headers.Add(header);
}
finally
{
slimLock.ExitWriteLock();
}
}
}
}
@ -142,6 +167,19 @@ namespace Squidex.Web.Pipeline
}
}
public void AddHeader(string header)
{
if (httpContextAccessor.HttpContext != null)
{
var cacheContext = httpContextAccessor.HttpContext.Features.Get<CacheContext>();
if (cacheContext != null)
{
cacheContext.AddHeader(header);
}
}
}
public void Finish(HttpContext httpContext, int maxSurrogateKeys)
{
Guard.NotNull(httpContext);

14
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherTests.cs

@ -49,7 +49,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
}
[Fact]
public async Task Should_not_invoke_steps()
public async Task Should_only_invoke_pre_enrich_for_empty_results()
{
var source = new IContentEntity[0];
@ -60,6 +60,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
await sut.EnrichAsync(source, requestContext);
A.CallTo(() => step1.EnrichAsync(requestContext))
.MustHaveHappened();
A.CallTo(() => step2.EnrichAsync(requestContext))
.MustHaveHappened();
A.CallTo(() => step1.EnrichAsync(requestContext, A<IEnumerable<ContentEntity>>._, A<ProvideSchema>._))
.MustNotHaveHappened();
@ -79,6 +85,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
await sut.EnrichAsync(source, requestContext);
A.CallTo(() => step1.EnrichAsync(requestContext))
.MustHaveHappened();
A.CallTo(() => step2.EnrichAsync(requestContext))
.MustHaveHappened();
A.CallTo(() => step1.EnrichAsync(requestContext, A<IEnumerable<ContentEntity>>._, A<ProvideSchema>._))
.MustHaveHappened();

24
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichForCachingTests.cs

@ -6,6 +6,7 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FakeItEasy;
@ -38,6 +39,29 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
sut = new EnrichForCaching(requestCache);
}
[Fact]
public async Task Should_add_cache_headers()
{
var headers = new List<string>();
A.CallTo(() => requestCache.AddHeader(A<string>._))
.Invokes(new Action<string>(header => headers.Add(header)));
await sut.EnrichAsync(requestContext);
Assert.Equal(new List<string>
{
"X-Flatten",
"X-Languages",
"X-NoCleanup",
"X-NoEnrichment",
"X-NoResolveLanguages",
"X-ResolveFlow",
"X-Resolve-Urls",
"X-Unpublished"
}, headers);
}
[Fact]
public async Task Should_add_app_version_and_schema_as_dependency()
{

47
backend/tests/Squidex.Web.Tests/Pipeline/CachingFilterTests.cs

@ -58,6 +58,53 @@ namespace Squidex.Web.Pipeline
Assert.Equal(StringValues.Empty, httpContext.Response.Headers[HeaderNames.ETag]);
}
[Fact]
public async Task Should_append_authorization_header_as_vary()
{
await sut.OnActionExecutionAsync(executingContext, Next());
Assert.Equal("Authorization", httpContext.Response.Headers[HeaderNames.Vary]);
}
[Fact]
public async Task Should_not_append_null_header_as_vary()
{
await sut.OnActionExecutionAsync(executingContext, () =>
{
cachingManager.AddHeader(null!);
return Task.FromResult(executedContext);
});
Assert.Equal("Authorization", httpContext.Response.Headers[HeaderNames.Vary]);
}
[Fact]
public async Task Should_not_append_empty_header_as_vary()
{
await sut.OnActionExecutionAsync(executingContext, () =>
{
cachingManager.AddHeader(string.Empty);
return Task.FromResult(executedContext);
});
Assert.Equal("Authorization", httpContext.Response.Headers[HeaderNames.Vary]);
}
[Fact]
public async Task Should_append_custom_header_as_vary()
{
await sut.OnActionExecutionAsync(executingContext, () =>
{
cachingManager.AddHeader("X-Header");
return Task.FromResult(executedContext);
});
Assert.Equal("Authorization,X-Header", httpContext.Response.Headers[HeaderNames.Vary]);
}
[Fact]
public async Task Should_not_append_etag_if_empty()
{

Loading…
Cancel
Save