HTTP File Response Improvements (#4465)

* Make local temp path configurable

* Generate entity tag based on file content hash instead of modified date
This commit is contained in:
Sipke Schoorstra 2023-09-21 19:42:41 +02:00 committed by GitHub
parent fc14cfb7b0
commit e7ca310eac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 33 additions and 6 deletions

View file

@ -1,3 +1,4 @@
using System.Security.Cryptography;
using Elsa.Extensions;
using Elsa.Http.Contracts;
using Elsa.Http.Models;
@ -8,6 +9,7 @@ using Elsa.Workflows.Core.Attributes;
using Elsa.Workflows.Core.Exceptions;
using Elsa.Workflows.Core.Models;
using FluentStorage.Blobs;
using FluentStorage.Utils.Extensions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
@ -141,10 +143,11 @@ public class WriteFileHttpResponse : Activity
try
{
// Send the zip stream the temporary file back to the client.
// Send the temporary file back to the client.
var contentType = zipBlob.Metadata["ContentType"];
var downloadAsFilename = zipBlob.Metadata["Filename"];
var eTag = $"\"{zipBlob.LastModificationTime?.ToString("O")}\"";
var hash = ComputeHash(zipStream);
var eTag = $"\"{hash}\"";
var eTagHeaderValue = new EntityTagHeaderValue(eTag);
await SendFileStream(context, httpContext, zipStream, contentType, downloadAsFilename, eTagHeaderValue);
@ -162,6 +165,16 @@ public class WriteFileHttpResponse : Activity
}
}
private string ComputeHash(Stream stream)
{
stream.Seek(0, SeekOrigin.Begin);
var bytes = stream.ToByteArray()!;
using var md5Hash = MD5.Create();
var hash = md5Hash.ComputeHash(bytes);
stream.Seek(0, SeekOrigin.Begin);
return Convert.ToBase64String(hash);
}
private async Task<(Blob, Stream, Func<ValueTask>)> GenerateZipFileAsync(ActivityExecutionContext context, HttpContext httpContext, ICollection<Func<ValueTask<Downloadable>>> downloadables)
{
var cancellationToken = context.CancellationToken;

View file

@ -27,6 +27,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Elsa.Http.Features;
@ -71,9 +72,10 @@ public class HttpFeature : FeatureBase
/// <summary>
/// A delegate to configure the <see cref="IFileCacheStorageProvider"/>.
/// </summary>
public Func<IServiceProvider, IFileCacheStorageProvider> FileCache { get; set; } = _ =>
public Func<IServiceProvider, IFileCacheStorageProvider> FileCache { get; set; } = sp =>
{
var blobStorage = StorageFactory.Blobs.DirectoryFiles(Path.GetTempPath());
var options = sp.GetRequiredService<IOptions<HttpFileCacheOptions>>().Value;
var blobStorage = StorageFactory.Blobs.DirectoryFiles(options.LocalCacheDirectory);
return new BlobFileCacheStorageProvider(blobStorage);
};

View file

@ -9,4 +9,9 @@ public class HttpFileCacheOptions
/// The time to live for cached files.
/// </summary>
public TimeSpan TimeToLive { get; set; } = TimeSpan.FromDays(7);
/// <summary>
/// The local cache directory. Defaults to the system's temp directory.
/// </summary>
public string LocalCacheDirectory { get; set; } = Path.GetTempPath();
}

View file

@ -39,7 +39,7 @@ internal class ZipManager
CancellationToken cancellationToken = default)
{
// Create a temporary file.
var tempFilePath = Path.GetTempFileName();
var tempFilePath = GetTempFilePath();
// Create a zip archive from the downloadables.
await CreateZipArchiveAsync(tempFilePath, downloadables, cancellationToken);
@ -54,7 +54,7 @@ internal class ZipManager
var zipStream = File.OpenRead(tempFilePath);
return (zipBlob, zipStream, () => Cleanup(tempFilePath));
}
/// <summary>
/// Loads a cached zip blob for the specified download correlation ID.
/// </summary>
@ -174,6 +174,13 @@ internal class ZipManager
return (downloadAsFilename, contentType);
}
private string GetTempFilePath()
{
var tempFileName = Path.GetRandomFileName();
var tempFilePath = Path.Combine(_fileCacheOptions.Value.LocalCacheDirectory, tempFileName);
return tempFilePath;
}
private void Cleanup(string filePath)
{
try