Browse Source

Etag support.

pull/320/head
Sebastian Stehle 7 years ago
parent
commit
7c74336ebf
  1. 3
      src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs
  2. 8
      src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs
  3. 8
      src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs
  4. 8
      src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs
  5. 11
      src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs
  6. 16
      src/Squidex/Areas/Frontend/Middlewares/WebpackMiddleware.cs
  7. 6
      src/Squidex/Config/Web/WebServices.cs
  8. 44
      src/Squidex/Pipeline/ETagFilter.cs
  9. 14
      src/Squidex/Pipeline/IGenerateEtag.cs

3
src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs

@ -14,6 +14,7 @@ using Squidex.Areas.Api.Controllers.Apps.Models;
using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Security;
using Squidex.Pipeline; using Squidex.Pipeline;
@ -62,6 +63,8 @@ namespace Squidex.Areas.Api.Controllers.Apps
var response = entities.Select(a => AppDto.FromApp(a, subject, appPlansProvider)).ToList(); var response = entities.Select(a => AppDto.FromApp(a, subject, appPlansProvider)).ToList();
Response.Headers["Etag"] = string.Join(";", response.Select(x => $"{x.Id}{x.Version}")).Sha256Base64();
return Ok(response); return Ok(response);
} }

8
src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs

@ -12,10 +12,11 @@ using NodaTime;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Pipeline;
namespace Squidex.Areas.Api.Controllers.Assets.Models namespace Squidex.Areas.Api.Controllers.Assets.Models
{ {
public sealed class AssetDto public sealed class AssetDto : IGenerateEtag
{ {
/// <summary> /// <summary>
/// The id of the asset. /// The id of the asset.
@ -97,6 +98,11 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models
/// </summary> /// </summary>
public long Version { get; set; } public long Version { get; set; }
public string GenerateETag()
{
return $"{Id}{Version}";
}
public static AssetDto FromAsset(IAssetEntity asset) public static AssetDto FromAsset(IAssetEntity asset)
{ {
return SimpleMapper.Map(asset, new AssetDto { FileType = asset.FileName.FileType() }); return SimpleMapper.Map(asset, new AssetDto { FileType = asset.FileName.FileType() });

8
src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs

@ -9,10 +9,11 @@ using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Pipeline;
namespace Squidex.Areas.Api.Controllers.Assets.Models namespace Squidex.Areas.Api.Controllers.Assets.Models
{ {
public sealed class AssetsDto public sealed class AssetsDto : IGenerateEtag
{ {
/// <summary> /// <summary>
/// The assets. /// The assets.
@ -25,6 +26,11 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models
/// </summary> /// </summary>
public long Total { get; set; } public long Total { get; set; }
public string GenerateETag()
{
return string.Join(";", Items?.Select(x => x.GenerateETag()) ?? Enumerable.Empty<string>()).Sha256Base64();
}
public static AssetsDto FromAssets(IResultList<IAssetEntity> assets) public static AssetsDto FromAssets(IResultList<IAssetEntity> assets)
{ {
return new AssetsDto { Total = assets.Total, Items = assets.Select(AssetDto.FromAsset).ToArray() }; return new AssetsDto { Total = assets.Total, Items = assets.Select(AssetDto.FromAsset).ToArray() };

8
src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs

@ -16,10 +16,11 @@ using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Pipeline;
namespace Squidex.Areas.Api.Controllers.Contents.Models namespace Squidex.Areas.Api.Controllers.Contents.Models
{ {
public sealed class ContentDto public sealed class ContentDto : IGenerateEtag
{ {
/// <summary> /// <summary>
/// The if of the content item. /// The if of the content item.
@ -79,6 +80,11 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
/// </summary> /// </summary>
public long Version { get; set; } public long Version { get; set; }
public string GenerateETag()
{
return $"{Id}{Version}";
}
public static ContentDto FromCommand(CreateContent command, EntityCreatedResult<NamedContentData> result) public static ContentDto FromCommand(CreateContent command, EntityCreatedResult<NamedContentData> result)
{ {
var now = SystemClock.Instance.GetCurrentInstant(); var now = SystemClock.Instance.GetCurrentInstant();

11
src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs

@ -5,9 +5,13 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Linq;
using Squidex.Infrastructure;
using Squidex.Pipeline;
namespace Squidex.Areas.Api.Controllers.Contents.Models namespace Squidex.Areas.Api.Controllers.Contents.Models
{ {
public sealed class ContentsDto public sealed class ContentsDto : IGenerateEtag
{ {
/// <summary> /// <summary>
/// The total number of content items. /// The total number of content items.
@ -18,5 +22,10 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
/// The content items. /// The content items.
/// </summary> /// </summary>
public ContentDto[] Items { get; set; } public ContentDto[] Items { get; set; }
public string GenerateETag()
{
return string.Join(";", Items?.Select(x => x.GenerateETag()) ?? Enumerable.Empty<string>()).Sha256Base64();
}
} }
} }

16
src/Squidex/Areas/Frontend/Middlewares/WebpackMiddleware.cs

@ -27,18 +27,18 @@ namespace Squidex.Areas.Frontend.Middlewares
public async Task Invoke(HttpContext context) public async Task Invoke(HttpContext context)
{ {
var buffer = new MemoryStream(); var responseBuffer = new MemoryStream();
var body = context.Response.Body; var responseBody = context.Response.Body;
context.Response.Body = buffer; context.Response.Body = responseBuffer;
await next(context); await next(context);
buffer.Seek(0, SeekOrigin.Begin); responseBuffer.Seek(0, SeekOrigin.Begin);
if (context.Response.StatusCode == 200 && IsIndex(context) && IsHtml(context)) if (context.Response.StatusCode == 200 && IsIndex(context) && IsHtml(context))
{ {
using (var reader = new StreamReader(buffer)) using (var reader = new StreamReader(responseBuffer))
{ {
var response = await reader.ReadToEndAsync(); var response = await reader.ReadToEndAsync();
@ -56,17 +56,17 @@ namespace Squidex.Areas.Frontend.Middlewares
context.Response.Headers["Content-Length"] = memoryStream.Length.ToString(); context.Response.Headers["Content-Length"] = memoryStream.Length.ToString();
await memoryStream.CopyToAsync(body); await memoryStream.CopyToAsync(responseBody);
} }
} }
} }
} }
else if (context.Response.StatusCode != 304) else if (context.Response.StatusCode != 304)
{ {
await buffer.CopyToAsync(body); await responseBuffer.CopyToAsync(responseBody);
} }
context.Response.Body = body; context.Response.Body = responseBody;
} }
private static string InjectStyles(string response) private static string InjectStyles(string response)

6
src/Squidex/Config/Web/WebServices.cs

@ -33,7 +33,11 @@ namespace Squidex.Config.Web
services.AddSingletonAs<RequestLogPerformanceMiddleware>() services.AddSingletonAs<RequestLogPerformanceMiddleware>()
.AsSelf(); .AsSelf();
services.AddMvc().AddMySerializers(); services.AddMvc(options =>
{
options.Filters.Add<ETagFilter>();
}).AddMySerializers();
services.AddCors(); services.AddCors();
services.AddRouting(); services.AddRouting();
} }

44
src/Squidex/Pipeline/ETagFilter.cs

@ -0,0 +1,44 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Squidex.Pipeline
{
public sealed class ETagFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var resultContext = await next();
var httpContext = context.HttpContext;
if (!httpContext.Response.Headers.TryGetValue("Etag", out _) && resultContext.Result is ObjectResult obj && obj.Value is IGenerateEtag g)
{
var calculatedEtag = g.GenerateETag();
if (!string.IsNullOrWhiteSpace(calculatedEtag))
{
httpContext.Response.Headers.Add("Etag", calculatedEtag);
}
}
if (httpContext.Request.Method == "GET" &&
httpContext.Request.Headers.TryGetValue("If-None-Match", out var noneMatch) &&
httpContext.Response.StatusCode == 200 &&
httpContext.Response.Headers.TryGetValue("Etag", out var etag) &&
!string.IsNullOrWhiteSpace(noneMatch) &&
!string.IsNullOrWhiteSpace(etag) &&
string.Equals(etag, noneMatch, System.StringComparison.Ordinal))
{
resultContext.Result = new StatusCodeResult(304);
}
}
}
}

14
src/Squidex/Pipeline/IGenerateEtag.cs

@ -0,0 +1,14 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Pipeline
{
public interface IGenerateEtag
{
string GenerateETag();
}
}
Loading…
Cancel
Save