Files
UnrealEngine/Engine/Source/Programs/UnrealCloudDDC/Jupiter/Implementation/Blob/RelayBlobStore.cs
2025-05-18 13:04:45 +08:00

135 lines
4.8 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Threading.Tasks;
using EpicGames.Horde.Storage;
using Microsoft.Extensions.Options;
namespace Jupiter.Implementation
{
public class RelayBlobStore : RelayStore, IBlobStore
{
public RelayBlobStore(IOptionsMonitor<UpstreamRelaySettings> settings, IHttpClientFactory httpClientFactory, IServiceCredentials serviceCredentials) : base(settings, httpClientFactory, serviceCredentials)
{
}
public Task<Uri?> GetObjectByRedirectAsync(NamespaceId ns, BlobId identifier)
{
// not supported
return Task.FromResult<Uri?>(null);
}
public Task<BlobMetadata> GetObjectMetadataAsync(NamespaceId ns, BlobId blobId)
{
// do not call into other instances to find metadata as we lack a endpoint for that
throw new BlobNotFoundException(ns, blobId);
}
public Task CopyBlobAsync(NamespaceId ns, NamespaceId targetNamespace, BlobId blobId)
{
throw new NotImplementedException();
}
public Task<Uri?> PutObjectWithRedirectAsync(NamespaceId ns, BlobId identifier)
{
// TODO: It could be useful to support relaying the presigned url
// not supported
return Task.FromResult<Uri?>(null);
}
public async Task<BlobId> PutObjectAsync(NamespaceId ns, byte[] blob, BlobId identifier)
{
using HttpRequestMessage putObjectRequest = await BuildHttpRequestAsync(HttpMethod.Put, new Uri($"api/v1/blobs/{ns}/{identifier}", UriKind.Relative));
putObjectRequest.Content = new ByteArrayContent(blob);
putObjectRequest.Content.Headers.ContentType = new MediaTypeHeaderValue(MediaTypeNames.Application.Octet);
putObjectRequest.Content.Headers.Add(CommonHeaders.HashHeaderName, identifier.ToString());
HttpResponseMessage response = await HttpClient.SendAsync(putObjectRequest);
response.EnsureSuccessStatusCode();
return identifier;
}
public Task<BlobId> PutObjectAsync(NamespaceId ns, ReadOnlyMemory<byte> blob, BlobId identifier)
{
return PutObjectAsync(ns, blob.ToArray(), identifier);
}
public async Task<BlobId> PutObjectAsync(NamespaceId ns, Stream content, BlobId identifier)
{
using HttpRequestMessage putObjectRequest = await BuildHttpRequestAsync(HttpMethod.Put, new Uri($"api/v1/blobs/{ns}/{identifier}", UriKind.Relative));
putObjectRequest.Content = new StreamContent(content);
putObjectRequest.Content.Headers.ContentType = new MediaTypeHeaderValue(MediaTypeNames.Application.Octet);
putObjectRequest.Content.Headers.Add(CommonHeaders.HashHeaderName, identifier.ToString());
HttpResponseMessage response = await HttpClient.SendAsync(putObjectRequest);
response.EnsureSuccessStatusCode();
return identifier;
}
public async Task<BlobContents> GetObjectAsync(NamespaceId ns, BlobId blob, LastAccessTrackingFlags flags, bool supportsRedirectUri = false)
{
using HttpRequestMessage getObjectRequest = await BuildHttpRequestAsync(HttpMethod.Get, new Uri($"api/v1/blobs/{ns}/{blob}", UriKind.Relative));
getObjectRequest.Headers.Add("Accept", MediaTypeNames.Application.Octet);
HttpResponseMessage response = await HttpClient.SendAsync(getObjectRequest);
if (response.StatusCode == HttpStatusCode.NotFound)
{
throw new BlobNotFoundException(ns, blob);
}
response.EnsureSuccessStatusCode();
long? contentLength = response.Content.Headers.ContentLength;
if (contentLength == null)
{
throw new Exception($"Content length missing in response from upstream blob store. This is not supported");
}
return new BlobContents(await response.Content.ReadAsStreamAsync(), contentLength.Value);
}
public async Task<bool> ExistsAsync(NamespaceId ns, BlobId blob, bool forceCheck)
{
using HttpRequestMessage headObjectRequest = await BuildHttpRequestAsync(HttpMethod.Head, new Uri($"api/v1/blobs/{ns}/{blob}", UriKind.Relative));
HttpResponseMessage response = await HttpClient.SendAsync(headObjectRequest);
if (response.StatusCode == HttpStatusCode.NotFound)
{
return false;
}
response.EnsureSuccessStatusCode();
return true;
}
public Task DeleteObjectAsync(IEnumerable<NamespaceId> namespaces, BlobId blob)
{
throw new NotImplementedException("DeleteObjectAsync from multiple namespaces is not supported on the relay blob store");
}
public Task DeleteObjectAsync(NamespaceId ns, BlobId blob)
{
throw new NotImplementedException("DeleteObjects is not supported on the relay blob store");
}
public Task DeleteNamespaceAsync(NamespaceId ns)
{
throw new NotImplementedException("DeleteNamespace is not supported on the relay blob store");
}
public IAsyncEnumerable<(BlobId, DateTime)> ListObjectsAsync(NamespaceId ns)
{
throw new NotImplementedException("ListObjects is not supported on the relay blob store");
}
}
}