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:
parent
fc14cfb7b0
commit
e7ca310eac
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue