diff --git a/Engine/Source/Programs/Horde/Horde.Server/Storage/Backends/AwsStorageBackend.cs b/Engine/Source/Programs/Horde/Horde.Server/Storage/Backends/AwsStorageBackend.cs index 040f20db2343..1aaf8687ff45 100644 --- a/Engine/Source/Programs/Horde/Horde.Server/Storage/Backends/AwsStorageBackend.cs +++ b/Engine/Source/Programs/Horde/Horde.Server/Storage/Backends/AwsStorageBackend.cs @@ -6,6 +6,7 @@ using System.Data.SqlTypes; using System.Globalization; using System.IO; using System.Runtime.CompilerServices; +using System.Text; using System.Threading; using System.Threading.Tasks; using Amazon; @@ -313,7 +314,7 @@ namespace Horde.Server.Storage.Backends } /// - public ValueTask TryGetReadRedirectAsync(string path, CancellationToken cancellationToken = default) => new ValueTask(GetPresignedUrl(path, HttpVerb.GET)); + public ValueTask TryGetReadRedirectAsync(string path, int? offset, int? length, CancellationToken cancellationToken = default) => new ValueTask(GetPresignedUrl(path, HttpVerb.GET, offset, length)); /// public ValueTask TryGetWriteRedirectAsync(string path, CancellationToken cancellationToken = default) => new ValueTask(GetPresignedUrl(path, HttpVerb.PUT)); @@ -321,7 +322,7 @@ namespace Horde.Server.Storage.Backends /// /// Helper method to generate a presigned URL for a request /// - Uri? GetPresignedUrl(string path, HttpVerb verb) + Uri? GetPresignedUrl(string path, HttpVerb verb, int? offset = null, int? length = null) { using TelemetrySpan span = OpenTelemetryTracers.Horde.StartActiveSpan($"{nameof(AwsStorageBackend)}.{nameof(GetPresignedUrl)}"); span.SetAttribute("path", path); @@ -334,6 +335,20 @@ namespace Horde.Server.Storage.Backends newGetRequest.BucketName = _options.AwsBucketName; newGetRequest.Key = fullPath; newGetRequest.Verb = verb; + if (offset != null || length != null) + { + StringBuilder range = new StringBuilder(); + if (offset != null) + { + range.Append(offset.Value); + } + range.Append("-"); + if (length != null) + { + range.Append(length.Value); + } + newGetRequest.Headers["Range"] = range.ToString(); + } newGetRequest.Expires = DateTime.UtcNow.AddHours(3.0); newGetRequest.ResponseHeaderOverrides.CacheControl = "private, max-age=2592000, immutable"; // 30 days diff --git a/Engine/Source/Programs/Horde/Horde.Server/Storage/Backends/FileStorageBackend.cs b/Engine/Source/Programs/Horde/Horde.Server/Storage/Backends/FileStorageBackend.cs index c68eb6d88c09..0cc076cac911 100644 --- a/Engine/Source/Programs/Horde/Horde.Server/Storage/Backends/FileStorageBackend.cs +++ b/Engine/Source/Programs/Horde/Horde.Server/Storage/Backends/FileStorageBackend.cs @@ -180,7 +180,7 @@ namespace Horde.Server.Storage.Backends } /// - public ValueTask TryGetReadRedirectAsync(string path, CancellationToken cancellationToken = default) => default; + public ValueTask TryGetReadRedirectAsync(string path, int? offset = null, int? length = null, CancellationToken cancellationToken = default) => default; /// public ValueTask TryGetWriteRedirectAsync(string path, CancellationToken cancellationToken = default) => default; diff --git a/Engine/Source/Programs/Horde/Horde.Server/Storage/Backends/MemoryStorageBackend.cs b/Engine/Source/Programs/Horde/Horde.Server/Storage/Backends/MemoryStorageBackend.cs index dafe23cbba5d..c0d98b99736e 100644 --- a/Engine/Source/Programs/Horde/Horde.Server/Storage/Backends/MemoryStorageBackend.cs +++ b/Engine/Source/Programs/Horde/Horde.Server/Storage/Backends/MemoryStorageBackend.cs @@ -78,7 +78,7 @@ namespace Horde.Server.Storage.Backends } /// - public ValueTask TryGetReadRedirectAsync(string path, CancellationToken cancellationToken = default) => default; + public ValueTask TryGetReadRedirectAsync(string path, int? offset = null, int? length = null, CancellationToken cancellationToken = default) => default; /// public ValueTask TryGetWriteRedirectAsync(string path, CancellationToken cancellationToken = default) => default; diff --git a/Engine/Source/Programs/Horde/Horde.Server/Storage/IStorageBackend.cs b/Engine/Source/Programs/Horde/Horde.Server/Storage/IStorageBackend.cs index 0c4a2d22edf1..50824919d682 100644 --- a/Engine/Source/Programs/Horde/Horde.Server/Storage/IStorageBackend.cs +++ b/Engine/Source/Programs/Horde/Horde.Server/Storage/IStorageBackend.cs @@ -72,9 +72,11 @@ namespace Horde.Server.Storage /// Gets a HTTP redirect for a read request /// /// Path to read from + /// Offset to start reading from + /// Length of data to read /// Cancellation token for the operation /// Path to upload the data to - ValueTask TryGetReadRedirectAsync(string path, CancellationToken cancellationToken = default); + ValueTask TryGetReadRedirectAsync(string path, int? offset, int? length, CancellationToken cancellationToken = default); /// /// Gets a HTTP redirect for a write request @@ -137,7 +139,7 @@ namespace Horde.Server.Storage public IAsyncEnumerable EnumerateAsync(CancellationToken cancellationToken = default) => _inner.EnumerateAsync(cancellationToken); /// - public ValueTask TryGetReadRedirectAsync(string path, CancellationToken cancellationToken = default) => _inner.TryGetReadRedirectAsync(path, cancellationToken); + public ValueTask TryGetReadRedirectAsync(string path, int? offset = null, int? length = null, CancellationToken cancellationToken = default) => _inner.TryGetReadRedirectAsync(path, offset, length, cancellationToken); /// public ValueTask TryGetWriteRedirectAsync(string path, CancellationToken cancellationToken = default) => _inner.TryGetWriteRedirectAsync(path, cancellationToken); diff --git a/Engine/Source/Programs/Horde/Horde.Server/Storage/StorageBackendProvider.cs b/Engine/Source/Programs/Horde/Horde.Server/Storage/StorageBackendProvider.cs index 1bb1aaeded41..69c971b37a4f 100644 --- a/Engine/Source/Programs/Horde/Horde.Server/Storage/StorageBackendProvider.cs +++ b/Engine/Source/Programs/Horde/Horde.Server/Storage/StorageBackendProvider.cs @@ -86,7 +86,7 @@ namespace Horde.Server.Storage public Task WriteAsync(string path, Stream stream, CancellationToken cancellationToken = default) => _backend.WriteAsync(path, stream, cancellationToken); /// - public ValueTask TryGetReadRedirectAsync(string path, CancellationToken cancellationToken = default) => _backend.TryGetReadRedirectAsync(path, cancellationToken); + public ValueTask TryGetReadRedirectAsync(string path, int? offset = null, int? length = null, CancellationToken cancellationToken = default) => _backend.TryGetReadRedirectAsync(path, offset, length, cancellationToken); /// public ValueTask TryGetWriteRedirectAsync(string path, CancellationToken cancellationToken = default) => _backend.TryGetWriteRedirectAsync(path, cancellationToken); diff --git a/Engine/Source/Programs/Horde/Horde.Server/Storage/StorageController.cs b/Engine/Source/Programs/Horde/Horde.Server/Storage/StorageController.cs index 119f38b5e27c..3b42ed4d1eba 100644 --- a/Engine/Source/Programs/Horde/Horde.Server/Storage/StorageController.cs +++ b/Engine/Source/Programs/Horde/Horde.Server/Storage/StorageController.cs @@ -276,7 +276,7 @@ namespace Horde.Server.Storage { if (storageClient is StorageClient storageClientImpl) { - Uri? redirectUrl = await storageClientImpl.GetReadRedirectAsync(locator, cancellationToken); + Uri? redirectUrl = await storageClientImpl.GetReadRedirectAsync(locator, offset, length, cancellationToken); if (redirectUrl != null) { return new RedirectResult(redirectUrl.ToString()); diff --git a/Engine/Source/Programs/Horde/Horde.Server/Storage/StorageService.cs b/Engine/Source/Programs/Horde/Horde.Server/Storage/StorageService.cs index 9cb9233baf34..2de53cf6bd63 100644 --- a/Engine/Source/Programs/Horde/Horde.Server/Storage/StorageService.cs +++ b/Engine/Source/Programs/Horde/Horde.Server/Storage/StorageService.cs @@ -98,9 +98,11 @@ namespace Horde.Server.Storage /// Gets a redirect for a read request /// /// Locator for the blob + /// Offset of the data to return + /// Length of the data to return /// Cancellation token for the operation /// Path to upload the data to - public abstract ValueTask GetReadRedirectAsync(BundleLocator locator, CancellationToken cancellationToken = default); + public abstract ValueTask GetReadRedirectAsync(BundleLocator locator, int? offset, int? length, CancellationToken cancellationToken = default); /// /// Gets a redirect for a write request @@ -153,7 +155,7 @@ namespace Horde.Server.Storage } /// - public override ValueTask GetReadRedirectAsync(BundleLocator locator, CancellationToken cancellationToken = default) => Backend.TryGetReadRedirectAsync(GetBlobPath(locator), cancellationToken); + public override ValueTask GetReadRedirectAsync(BundleLocator locator, int? offset = null, int? length = null, CancellationToken cancellationToken = default) => Backend.TryGetReadRedirectAsync(GetBlobPath(locator), offset, length, cancellationToken); /// public override async Task> ReadBundleRangeAsync(BundleLocator locator, int offset, int length, CancellationToken cancellationToken = default)