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)